Normally, we're restricted from discussing Apple prerelease stuff, but I've already seen plenty of SwiftUI discussions, so I suspect that it's OK; just this once.
I am in the process of driving into the weeds on one of the tutorials (I do that).
I am adding a pair of buttons below the swipeable screens in the "Interfacing With UIKit" tutorial: https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit
These are "Next" and "Prev" buttons. When at one end or the other, the corresponding button hides. I have that working fine.
The problem that I'm having, is accessing the UIPageViewController instance represented by the PageViewController.
I have the currentPage property changing (by making the PageViewController a delegate of the UIPageViewController), but I need to force the UIPageViewController to change programmatically.
I know that I can "brute force" the display by redrawing the PageView body, reflecting a new currentPage, but I'm not exactly sure how to do that.
struct PageView<Page: View>: View { var viewControllers: [UIHostingController<Page>] @State var currentPage = 0 init(_ views: [Page]) { self.viewControllers = views.map { UIHostingController(rootView: $0) } } var body: some View { VStack { PageViewController(controllers: viewControllers, currentPage: $currentPage) HStack(alignment: .center) { Spacer() if 0 < currentPage { Button(action: { self.prevPage() }) { Text("Prev") } Spacer() } Text(verbatim: "Page \(currentPage)") if currentPage < viewControllers.count - 1 { Spacer() Button(action: { self.nextPage() }) { Text("Next") } } Spacer() } } } func nextPage() { if currentPage < viewControllers.count - 1 { currentPage += 1 } } func prevPage() { if 0 < currentPage { currentPage -= 1 } } }
I know the answer should be obvious, but I'm having difficulty figuring out how to programmatically refresh the VStack or body.
you add the modifier . refreshable and you provide a function to refresh the content inside the closure. As I told you, this feature is powered by async/await, so we need the new await keyword before the function call.
To conform to ObservableObject, simply add it to the class definition. ObservableObject provides a default implementation for objectWillChange using Combine/ObservableObjectPublisher. To trigger objectWillChange events when your data changes, annotate your properties with the @Published property wrapper.
So, to answer the question directly: yes, you should get busy learning SwiftUI because it is the future of app development on Apple's platforms, but at some point you still need to learn UIKit because those skills will be useful for years to come.
SwiftUI is Apple's brand new framework for building user interfaces for iOS, tvOS, macOS, and watchOS. Apple introduced SwiftUI in 2019 and the framework has been evolving at a rapid pace ever since.
IMPORTANT THING! If you search for this hack, probably you doing something wrong! Please, read this block before you read hack solution!!!!!!!!!!
Your UI wasn't updated automatically because of you miss something important.
- Your ViewModel must be a
class
wrapped intoObservableObject
/Observed
object- Any field in ViewModel must be a
STRUCT
. NOT ACLASS
!!!! Swift UI does not work with classes!- Must be used modifiers correctly (state, observable/observedObject, published, binding, etc)
- If you need a
class
property in your View Model (for some reason) - you need to mark it asObservableObject
/Observed
object and assign them into View's object !!!!!!!! insideinit()
ofView
. !!!!!!!- Sometimes is needed to use hacks. But this is really-really-really exclusive situation! In most cases this wrong way! One more time: Please, use
struct
s instead of classes!Your UI will be refreshed automatically if all of written above was used correctly.
Sample of correct usage:
struct SomeView : View { @ObservedObject var model : SomeViewModel @ObservedObject var someClassValue: MyClass init(model: SomeViewModel) { self.model = model //as this is class we must do it observable and assign into view manually self.someClassValue = model.someClassValue } var body: some View { //here we can use model.someStructValue directly // or we can use local someClassValue in view, but not value from model } } class SomeViewModel : ObservableObject { @Published var someStructValue: Bool var someClassValue: MyClass = NewMyClass //myClass : ObservableObject }
(hacks solutions - prefer do not use this)
Way 1: declare inside of view:
@State var updater: Bool = false
all you need to update is toogle() it: updater.toogle()
Way 2: refresh from ViewModel
Works on SwiftUI 2
public class ViewModelSample : ObservableObject func updateView(){ self.objectWillChange.send() } }
Way 3: refresh from ViewModel:
works on SwiftUI 1
import Combine import SwiftUI class ViewModelSample: ObservableObject { private let objectWillChange = ObservableObjectPublisher() func updateView(){ objectWillChange.send() } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With