young
(rtSwift)
1
I can't figure why my code does this:
struct CounterView: View {
// both are ":"!
let (timeSeparator, clockDisplayTemplate) = {
print("HERE HERE") // this is printed twice in console
return (":", "88:88:88")
}()
// ....
}
HERE HERE is printed twice in the console and timeSeparator, clockDisplayTemplate both are ";". This works correctly if I just extract my code out to a playground. So this oddity reproduce only with my code specifically but I just could figure why, and why does the closure run twice?
My complete code
import SwiftUI
import ComposableArchitecture
...
struct CounterView: View {
let store: StoreOf<CounterFeature>
let accentColor = Color.pink
let counterFont = Font.custom("Digital-7Monoitalic", fixedSize: 45).monospacedDigit()
let clockFont = Font.custom("Digital-7Monoitalic", fixedSize: 215).monospacedDigit()
let (timeSeparator, clockDisplayTemplate) = {
print("HERE HERE") // this is printed twice in console
return (":", "88:88:88")
}()
var body: some View {
WithViewStore(self.store, observe: { $0 }) { store in
VStack(spacing: 5) {
Text("timeSeparator: \(timeSeparator)") // ":"
Text("clockDisplayTemplate: \(clockDisplayTemplate)") // this is also ":"
TimelineView(.periodic(from: .now, by: 0.2)) { context in
clockDisplay(date: context.date)
}
HStack(spacing: 15) {
Button {
store.send(.decrementButtonTapped, animation: .spring)
} label: {
Image(systemName: "minus")
.font(counterFont)
}
.buttonStyle(.symbolButtonStyle)
.buttonRepeatBehavior(.enabled)
.sensoryFeedback(.decrease, trigger: store.count)
counterDisplay(store: store)
Button {
store.send(.incrementButtonTapped, animation: .spring)
} label: {
Image(systemName: "plus")
.font(counterFont)
}
.buttonStyle(.symbolButtonStyle)
.buttonRepeatBehavior(.enabled)
.sensoryFeedback(.increase, trigger: store.count)
}
.padding(.horizontal)
}
}
}
func clockDisplay(date: Date) -> some View {
Text(clockDisplayTemplate)
.font(clockFont)
.lineLimit(1)
.padding(.horizontal, 20)
.minimumScaleFactor(0.2)
.foregroundColor(.gray)
.opacity(0.25)
.overlay {
// this does 24-hour style. Who figured this out?
// https://forums.swift.org/t/new-date-formatstyle-anyway-to-do-24-hour/52994/34
Text(date,
format: Date.VerbatimFormatStyle(format: "\(hour: .twoDigits(clock: .twentyFourHour, hourCycle: .oneBased)):\(minute: .twoDigits):\(second: .twoDigits)",
timeZone: .current,
calendar: .current)
)
.font(clockFont)
.lineLimit(1)
.padding(.horizontal, 20)
.minimumScaleFactor(0.2)
.foregroundColor(accentColor)
.contentTransition(.numericText(value: date.timeIntervalSince1970.rounded()))
.animation(.spring, value: date.timeIntervalSince1970)
}
}
func counterDisplay(store: ViewStore<CounterFeature.State, CounterFeature.Action>) -> some View {
// Use this view for sizing
Text(90_000.formatted().dropFirst())
.font(counterFont)
.lineLimit(1)
.foregroundColor(.gray)
.opacity(0.25)
.overlay {
Text(store.count, format: .number)
.font(counterFont)
.foregroundColor(accentColor)
.frame(maxWidth: .infinity, alignment: .trailing)
.contentTransition(.numericText(value: Double(store.count)))
}
.padding(.init(top: 15, leading: 20, bottom: 15, trailing: 20))
.background {
RoundedRectangle(cornerRadius: 20, style: .continuous)
.strokeBorder(accentColor, lineWidth: 2)
}
}
}
#Preview {
CounterView(
store: Store(initialState: CounterFeature.State()) {
CounterFeature()
}
)
}
sveinhal
(Svein Halvor Halvorsen)
2
Why both values are :, I don't know, but with regards to the double printing, I don't think you can rely on your view struct being initialized only once, or a certain number of times. I think SwiftUI is free to reevaluate the view hierarchy description whenever it pleases, and that includes both initialising the view structs, as well as evaluating the body property of those views.
2 Likes
young
(rtSwift)
3
It's fine if the CounterView is created twice.
I just cannot figure out why clockDisplayTemplate is ":" and not "88:88:88"
if I make it a static let, it worked as expected.
why is this so? Since if I remember correctly, static let are lazy. Maybe this is why it work?
But non-static let should work:
import SwiftUI
struct FooView: View {
let (timeSeparator, clockDisplayTemplate) = {
print("HERE HERE") // this is printed twice in console
let result = (":", "88:88:88")
print(" result: \(result)")
return result
}()
var body: some View {
VStack {
Text("timeSeparator: \(timeSeparator)")
Text("clockDisplayTemplate: \(clockDisplayTemplate)")
}
}
}
#Preview {
FooView()
}
This works as expected.
Why is let fail, static let works?
tera
4
Could you make a minimal example that still doesn't work for you? I tried your last fragment - it works fine for me.
young
(rtSwift)
5
Just adding in the ComposableArchitecture cause the problem to occur:
In a new iOS app Xcode project, add package The Composable Architecture
Add a new FooBarView and copy paste this:
import SwiftUI
import ComposableArchitecture
struct FooBarView: View {
// just adding this cause the problem
let store: StoreOf<CounterFeature>
// doing it this way is no problem
// let store: StoreOf<CounterFeature> = Store(initialState: CounterFeature.State()) {
// CounterFeature()
// }
// adding in TCA, problem here: both get ":"
let (timeSeparator, clockDisplayTemplate) = (":", "88:88:88")
static let (aaa, bbb) = (":", "88:88:88")
var body: some View {
VStack {
Text(Date.now, format: .dateTime.hour().minute().second())
Color.clear.frame(height: 30)
Text("timeSeparator \"\(timeSeparator)\"")
Text("clockDisplayTemplate \"\(clockDisplayTemplate)\"")
Divider()
Text("aaa \"\(Self.aaa)\"")
Text("bbb \"\(Self.bbb)\"")
}
.padding()
}
}
struct CounterFeature: Reducer {
struct State: Equatable {
var count = 99
var numberFactAlert: String?
}
enum Action: Equatable {
case decrementButtonTapped
case incrementButtonTapped
}
func reduce(into state: inout State, action: Action) -> Effect<Action> {
switch action {
case .decrementButtonTapped:
state.count -= 1
if state.count < 0 {
state.count = 9_999
}
return .none
case .incrementButtonTapped:
state.count += 1
if state.count > 9_999 {
state.count = 0
}
return .none
}
}
}
#Preview {
// FooBarView()
FooBarView(
store: Store(initialState: CounterFeature.State()) {
CounterFeature()
}
)
}
So how can this be?
1 Like
mbrandonw
(Brandon Williams)
6
Hi @young, this has nothing to do with the Composable Architecture. It is reproducible by just holding onto any value in the view:
struct FooBarView: View {
let value: Int
let (timeSeparator, clockDisplayTemplate) = (":", "88:88:88")
static let (aaa, bbb) = (":", "88:88:88")
…
}
No idea why this is happening, but it does not have to do with our library.
1 Like
young
(rtSwift)
7
I see. Thank you!
struct FooBarView: View {
// what's the difference between this which cause this problem
let value: Int
// add this and problem goes away!
let xxx = 1 // assigning here doesn't cause this problem
let (timeSeparator, clockDisplayTemplate) = (":", "88:88:88")
static let (aaa, bbb) = (":", "88:88:88")
…
}
Test
import SwiftUI
struct FooView: View {
let value = 1
let xxx = 1
let (a, b) = (":", "88:88:88")
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
Text("a = \(a)")
Text("b = \(b)")
}
.padding()
}
}
#Preview {
FooView()
// FooView(value: 1)
}
tera
8
This one works fine for me as well. I am on Xcode 14.3. Could be a bug in a more recent Xcode version?
young
(rtSwift)
9
I'm on Xcode Version 15.0 (15A240d)
I hope this is a bug that can be fix.
young
(rtSwift)
10
This looks like a bug in Xcode Version 15.0 (15A240d). I can even make the compiler fail with nonzero exit:
import SwiftUI
struct ContentView: View {
// what's the difference between this which cause this problem
let value: Int
// if doing it this way, no problem
// let value = 1
// all these tuple destructure the 2nd and subsequent elements are wrong!
let (timeSeparator, clockDisplayTemplate) = (":", "88:88:88")
let (aaa, bbb) = (":", "88:88:88")
let (xxx, yyy) = ("one", 1)
// uncomment this, compiler fail with nonzero exit!!!!
// let (z1, z2, z3) = ("one", 1, Double.pi)
var body: some View {
VStack {
Text("timeSeparator = \(timeSeparator)")
Text("clockDisplayTemplate = \(clockDisplayTemplate)") // !! wrong
Divider()
Text("aaa = \(aaa)")
Text("bbb = \(bbb)") // !! Wrong
Text("xxx = \(xxx)")
Text("yyy = \(yyy)") // Wrong
// Text("z1 = \(z1)")
// Text("z2 = \(z2)")
// Text("z3 = \(z3)")
}
.padding()
}
}
#Preview {
ContentView(value: 123)
}
How to report this bug? Xcode, Swift compiler?
eskimo
(Quinn “The Eskimo!”)
11
I can even make the compiler fail with nonzero exit
Well, that’s not good.
How to report this bug? Xcode, Swift compiler?
My standard advice is:
Either way, please post your bug number, just for the record.
Share and Enjoy
Quinn “The Eskimo!” @ DTS @ Apple
1 Like
young
(rtSwift)
12
I can reproduce with minimal command line code:
struct Foo {
let value: Int
let (aaa, bbb) = ("aaa", "bbb") // bbb is wrong! it's "aaa" but should be "bbb"
// uncomment this to make the compiler fail with non-zero exit
// let (z1, z2, z3) = ("one", 1, Double.pi)
func tellMe() {
print(foo.aaa)
print(foo.bbb) // output: aaa
assert(aaa == "aaa")
assert(bbb == "bbb", "bbb should be bbb but it's \(bbb)")
}
}
let foo = Foo(value: 1)
foo.tellMe()
print("Hello")
I'm not sure if incorrect tuple destructure and the compiler non-zero exit are related so I filed two bugs:
FB13224203 compiler non-zero exit
FB13224237 incorrect tuple destructure
Bug report doesn't ask which version of Xcode. I'm on Xcode Version 15.0 (15A240d)
Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1)
xwu
(Xiaodi Wu)
13
I think @eskimo's advice, then, would be to file this against Swift rather than Xcode. Indeed, I can minimize your example further:
struct Foo {
let value: Int
let (aaa, bbb) = ("aaa", "bbb")
// let (z1, z2, z3) = ("one", 1, Double.pi)
}
let foo = Foo(value: 1)
print(foo.bbb) // "aaa"
This didn't compile at all before Swift 5.6, and has given the unexpected value ever since then. Which is not great...
4 Likes
tera
14
Was it ever working for you? Testing on godbolt on various swift versions it either results either "Foo cannot be constructed" or a compiler crash or incorrect app (your assert fires).
young
(rtSwift)
15
I report the bugs by following the url in the error message. I can choose the Swift category.
Please submit a bug report (https://swift.org/contributing/#reporting-bugs) and include the crash backtrace.
Should I file these bugs on GitHub?
Compiler non-zero exit
Incorrect tuple destructure
Joe_Groff
(Joe Groff)
16
Thanks for bringing this bug to our attention! I looked through the affected code's history, and it seems to confirm what Xiaodi said, that this particular case of a destructuring stored let property binding in a struct has never been implemented correctly. I'm trying out a patch to at least raise a compiler error so people don't run into the miscompile: [wip] disable let stored property destructuring by jckarter · Pull Request #68930 · apple/swift · GitHub
6 Likes
xwu
(Xiaodi Wu)
17
@Joe_Groff Question (probably better posted on the PR, but I'm not logged into GitHub on this device): does this destructuring bug really only affect structs?
1 Like
Joe_Groff
(Joe Groff)
18
The bug is in the code generation for the implicit elementwise initializer, which is only generated for structs.
3 Likes
young
(rtSwift)
19
It works fine for static let (aaa, bbb) = ("aaa", "bbb")
Joe_Groff
(Joe Groff)
20
Static properties are fine. This patch won't prevent those from compiling.