How do I trigger updateUIView of a UIViewRepresentable?
You need to create UIKit view inside makeUIView
and via Binding pass only dependent data. That binding change, when related state - source of truth - changed, calls updateUIView
, where you should update your UIKit view.
Here is simplified demo sketch only, to show concept (might have typos):
struct SpritzUIViewRepresentable : UIViewRepresentable{
@Binding var currentWord: SpritzSwiftWord
@Binding var backgroundColor: UIColor
func makeUIView(context: Context) -> SpritzSwiftView {
// create and configure view here
return SpritzSwiftView(frame: CGRect.zero) // frame does not matter here
}
func updateUIView(_ uiView: SpritzSwiftView, context: Context) {
// update view properties here from bound external data
uiView.backgroundColor = backgroundColor
uiView.updateWord(currentWord)
}
}
and button now should just change model data
VStack {
Text("SpritzTest")
.padding()
SpritzUIViewRepresentable(backgroundColor: $backgroundColor, SpritzViewManager:$ssManager, currentWord: $currentWord)
.padding()
Button(action:
{
ssManager = SpritzSwiftManager(withText: "Text try one two three", andWordPerMinute: 200)
self.backgroundColor = .clear
ssManager.startReading { (word, finished) in
if !finished {
self.currentWord = word
}
}
})
{
Text("Start")
}
assuming updated properties
@State private var currentWord = SpritzSwiftWord(word: "")
@State private var backgroundColor = UIColor.white // whatever you want
Update UIView (inside UIViewRepresentable) when @Binding value changes
You don't need to use onReceive
and watch the publisher -- updateUIView
will be called when selectedValue
changes, so you can use it directly:
struct ContentView: View {
@State private var selected = "3"
var body: some View {
CustomPickerView(data: ["1","2","3"], selectedValue: $selected)
Button("Choose 1") {
selected = "1"
}
}
}
struct CustomPickerView: UIViewRepresentable {
let data: [String]
@Binding var selectedValue: String
//makeCoordinator()
func makeCoordinator() -> CustomPickerView.Coordinator {
return CustomPickerView.Coordinator(self)
}
//makeUIView(context:)
func makeUIView(context: UIViewRepresentableContext<CustomPickerView>) -> UIPickerView {
let picker = UIPickerView(frame: .zero)
// allows rows to be compressed in swiftUI
picker.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
picker.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
// allows rows to be expanded
picker.setContentHuggingPriority(.defaultLow, for: .horizontal)
picker.dataSource = context.coordinator
picker.delegate = context.coordinator
picker.selectRow(data.firstIndex(of: selectedValue) ?? 0, inComponent: 0, animated: false)
return picker
}
func updateUIView(_ view: UIPickerView, context: UIViewRepresentableContext<CustomPickerView>) {
view.selectRow(data.firstIndex(of: selectedValue) ?? 0, inComponent: 0, animated: true)
}
class Coordinator : NSObject, UIPickerViewDataSource, UIPickerViewDelegate {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
parent.data.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String?
{
parent.data[row]
}
var parent: CustomPickerView
init(_ input : CustomPickerView) {
self.parent = input
}
}
}
iOS 14 SwiftUI UIViewRepresentable updateUIView doesn't detect ObservedObject changing?
You need to make ˋCustomTextViewˋ to listen to changes of ˋTmpTextViewModelˋ.
Just declare ˋtmpTextVMˋ in ˋCustomTextViewˋ as ˋ@ObservedOjectˋ.
struct CustomTextView: UIViewRepresentable {
@ObservedObject var tmpTextVM: TmpTextViewModel
//....
}
Update Binding in UIViewRepresentable
Looks like SwiftUI is doing some cleverness under the hood that isn't immediately obvious to us. Because you don't actually use your binding in updateUIView
, it's not actually getting called.
However, if you update your code to the following:
func updateUIView(_ uiView: UIViewType, context: Context) {
print("Hello \(status)")
}
then you'll see that it does, in fact, get called.
PS - you can use status.toggle()
instead of status = !status
SwiftUI: updateUIView() function in UIViewRepresentable causes app to freeze and CPU to spike
In your updateUIView
, you're setting a value to self.height
, which is a Binding. My guess is that the @Binding is connected to a property (either another @Binding or a @State on your surrounding view). So, whenever you set a new value to that @Binding, that triggers a refresh of the parent view. That, in turn, ends up calling updateUIView
again, and you get into an infinite loop.
How to solve it probably depends on your architecture needs for the program. If you can get away with not having the parent know the height, you can probably solve it just by having the view update its own height.
You could also try only setting self.height
to a new value if it != the old one -- that would probably short circuit the loop. But, you might end up with other side effects.
UIView does not update after updateUIView is called on UIViewRepresentable
I built the code to my iPhone and it worked on there. I guess it was just broken on the simulator.
How to update UIViewRepresentable with ObservableObject
To make sure your ObservedObject
does not get created multiple times (you only want one copy of it), you can put it outside your UIViewRepresentable
:
import SwiftUI
import MapKit
struct ContentView: View {
@ObservedObject var dataSource = DataSource()
var body: some View {
MyView(locationCoordinates: dataSource.locationCoordinates, value: dataSource.value)
}
}
class DataSource: ObservableObject {
@Published var locationCoordinates = [CLLocationCoordinate2D]()
var value: Int = 0
init() {
Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { timer in
self.value += 1
self.locationCoordinates.append(CLLocationCoordinate2D(latitude: 52, longitude: 16+0.1*Double(self.value)))
}
}
}
struct MyView: UIViewRepresentable {
var locationCoordinates: [CLLocationCoordinate2D]
var value: Int
func makeUIView(context: Context) -> MKMapView {
MKMapView(frame: .zero)
}
func updateUIView(_ view: MKMapView, context: Context) {
print("I am being called!")
let newestCoordinate = locationCoordinates.last ?? CLLocationCoordinate2D(latitude: 52, longitude: 16)
let annotation = MKPointAnnotation()
annotation.coordinate = newestCoordinate
annotation.title = "Test #\(value)"
view.addAnnotation(annotation)
}
}
Related Topics
How to Move Application's Window Between Virtual Desktops in Os X
Sharing Highscore with Social Media
Background Upload with Share Extension
How to Unwrap Arbitrarily Deeply Nested Optionals in Swift
Swift/Cloudkit: After Record Changed, Upload Triggers "Service Record Changed"
Why Can't I Use Self in a Func Swift
Notification Extension Access Core Data
Difference Between Optional Values in Swift
Solving System of Equations in Swift
Play Mp4 Using Mpmovieplayercontroller() in Swift
How to Cast from Uint16 to Nsnumber
How to Select All Text in a Uitextfield Using Swift
Using 'If-Case' Format in Boolean Assignment in Swift
How to Skip Iterations of a For-In Loop (Swift 3)