Well, SwiftUI is certainly a big improvement on building maintainable, robust and nice user interfaces on iOS and macOS compared to the old MVC architecture (Massive View Controller) and the NIB (Next Interface Builder). But it is mainly a declarative framework and from the application developer’s point of view it’s sometimes hard to anticipate what is really going on behind the scenes and especially when its going to happen. There are situations where the promised behaviour doesn’t take place and I’ve already struggled hours for hours to look for a workaround.
Especially the .onAppear
modifier seems to be problematic. It looks as if the supplied closure will be executed too early sometimes and the statements inside do not have the desired effect.
For example, if you want set the focus on a text field programmatically you probably start with such a sequence:
1 2 3 4 5 6 7 8 9 10 11 |
@FocusState private var focus: Bool var body: some View { … TextField("", text: $xyz) .focused($focus) … .onAppear() { focus = true } } |
Quite often, this doesn’t work as expected. The focus is just not set when the text field appears on the screen. When you look around in the internet for a solution you’ll probably find the hint to delay the setting of the focus a little bit like
1 2 3 4 5 |
.onAppear() { Timer.scheduledTimer(withTimeInterval: 0.7, repeats: false) { _ in focus = true } } |
Funny enough, this seems to work but its really ugly or don’t you think so. Not perfect but a little bit better is the following sequence:
1 2 3 4 5 |
.onAppear() { DispatchQueue.main.async { focus = true } } |
Here, the focus setting is postponed to one of the next dispatch cycles on the main queue and that fixes the problem. This is not the only place where such problems arise. I found similar situations with scroll proxies and model updates where this solution made the change.
So, this is a rule of thumb for me on SwiftUI. If I encounter a problem where some statements seem to miss the desired effect I try to enclose the statements in
1 2 3 |
DispatchQueue.main.async { … } |
and sometimes it helps.