SwiftUI wrapping UIViewController using UIViewControllerRepresentable - swiftui

I have question about wrapping custom UIViewControllers into SwiftUI view using UIViewControllerRepresentable. Does anyone have issue with UITextFields in such case?
I have situation that sometimes this textfield works normally when wrapped and otherwise they just work for a while and stopped working after editinig text for about 10sec or hiding/showing keyboard, or they doesn't work at all.
Situation seems to be very odd.

Ok it was the problem with
override var canBecomeFirstResponder: Bool { true }
on UIViewController
https://developer.apple.com/documentation/uikit/uiresponder/1621130-canbecomefirstresponder

Related

Is SwiftUI analogous to React when it comes to rendering? does it renders only the pieces that needs change?

The information I got about react is that even if you have a large component with lots of subcomponents when the value state of one of those components change react is smart enough to update only the part of the component that needs change instead of the whole component thus being really performant during UI re-render,
My question is does SwiftUI works the same way?
If I have a Text() that is updated by #Published property inside an Observed class the value happens to be the same as before will the UI actually re-render?
what if
class StringFetcher: ObservableObject {
#Published var stringA: String = "foo"
#Published var stringB: String = "bar"
#Publisher var showScreenA: Bool = true
}
struct MyView: View {
#ObservedObject var fetcher: StringFetcher
var body: some View {
VStack {
if fetcher.showScreenA {
Text(fetcher.stringA)
} else {
Text(fetcher.stringB)
}
}
}
}
Will a change in stringB publisher trigger an UI re-rendering even if B isn't visible? at the moment?
I couldn't find much resource on how the process works, does anyone know that or know where I could read more in depth about it?
Thank you in advance )
Yes if you supply the same data to a View init as last time (e.g. the same string to Text), SwiftUI will not run the body of that View and thus that part of the View hierarchy diff will not have changed and thus it won't update those UIKit views on screen. However, if you supply the same number to Text and the region changes, then it will run its own body because it also is listening for region changes so it can update UILabel with new number formatting.
Some property wrappers cause body to run every time though, so sometimes you need to do some defensive sub View wrapping to prevent body being called unnecessarily, e.g. #FetchRequest has this problem.
Swift works most efficiently with value types like structs so always try to use those instead of objects. With property wrappers and DynamicProperty you can usually do everything in the View struct, especially now we have async/await and the task modifier.

viewWillAppear() or other equivalent in SwiftUI

I'm looking for an event in the life-cycle of a view in SwiftUI, that is equivalent to viewWillAppear().
Yes, I know there is an ".OnAppear" in SwiftUI, but that is like viewDidAppear.
Is there a workaround to do get that specific execution time before view is loaded?
Try this modifier:
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
// your code
}

SwiftUi - prevent update

Basic question: how can I prevent a SwiftUI subview to redraw when the parent redraws?
I am experiencing performance issues on my project because so many nested views are constantly redrawn. Now, so of them are redrawn without anything being changed. To debug my problem, I even tried to simplify the problem to its core:
struct StupidView: View {
init() {
print("redraw")
}
var body: some View {
ZStack{}
}
}
This empty view, of course, does not have any moving part that requires redrawing, but it gets redrawn every time its parent is redrawn.
I even tried to add a .id(1) to it with no results. Of course, my problem is with complex views whose useless redrawing slows down the app. How to not redraw parts of a view?
Initializing a View has no meaning of rendering!
It does not rendered but got initialized try this down code, in SwiftUI initializing a View is pretty normal thing! A View could get Initializied 100 times but it will rendered if it needs! if your View has complex Content that SwiftUI get confused we can help SwiftUI in this way that make our View Equatable, then SwiftUI will understand when really need to rendered!
struct StupidView: View {
init() {
print("initializing!")
}
var body: some View {
print("rendering!")
return ZStack{}
}
}

SwiftUI SecureField: How to achieve the same character obscuring behaviour as in UIKit?

My problem is SecureField in SwiftUI doesn’t display characters input by the user for any time at all, it just directly shows the '•' symbol for each character as it's typed - whereas in UIKit, UITextField (with isSecureTextEntry = true) shows the latest character for a second before hiding it behind '•'.
UX testers at my company have requested I bring back the "old behaviour" - but this behaviour doesn't seem part of any public API.
Interestingly this goes for UITextField custom classes injected into SwiftUI using UIViewRepresentable too - they behave in the "SwiftUI way" described above. So there's some contextual behaviour modification going on in SwiftUI for all secure UITextField behaviour? I'd have to completely rewrite my SwiftUI form into a full UIViewController to get back the behaviour (modally pushed UIViewControllers with secure UITextFields do exhibit the desired behaviour.)
Is this a sort of sideline bug in SwiftUI? I see the same thing for SwiftUI in both iOS13 and 14. Anyone seen a workaround or solution?
-EDIT-
After #Asperi's great explanation below, I noticed that my UITextField custom classes injected into SwiftUI using UIViewRepresentable were forcing this behaviour by unnecessarily setting the text binding in the updateUIView call. Using a Coordinator only to deal with text logic fixed the problem for me when using this method.
The observed effect is due to immediate apply to bound string state and immediate react/rebuild of view.
To bring desired behavior beck we need to postpone somehow state update and thus give a chance for SecuredField/UITextField to update self without synchronisation with state.
Here is a demo of possible direction (it is not ideal, but a way to go). Tested with Xcode 12.1 / iOS 14.1.
struct DemoSecureFieldView: View {
#State private var password = "demo"
var textBinding: Binding<String> {
Binding(get: { password },
set: { value in
// more logic can be added to delay _only_ if new symbol added,
// and force apply if next symbol came fast
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
password = value
}
}
)
}
var body: some View {
VStack {
SecureField("Placeholder", text: textBinding)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
}.background(Color.pink)
}
}

What is the equivalent of WKInterfaceController.didAppear() in SwiftUI on watchOS?

I'm building an Apple Watch app, and there is code I want to run every time the app is brought to the foreground.
Previously, if I wanted to do this in a watchOS with a WKInterfaceController, I would put this code in didAppear().
In SwiftUI, there is onAppear(), but when I call that on watchOS it only seems to be called the first time the app loads up, so it behaves like WKInterfaceController.willActivate() instead. The app has just a single view.
If onAppear() is the equivalent of WKInterfaceController.willActivate(), is there a different SwiftUI function that is the equivalent of WKInterfaceController.didAppear()?
Here's my current example code:
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello World").font(.footnote)
.onAppear {
print("onAppear called")
}
}
}
In the meantime, I am going to experiment with triggering what I need to do within the ExtensionDelegate, but I'm just trying to learn my way around SwiftUI on WatchOS, so knowing the answer to this would be helpful in the future.