4

I'm trying to get a nice transition for a view that needs to display date. I give an ID to the view so that SwiftUI knows that it's a new label and animates it with transition. Here's the condensed version without formatters and styling and with long duration for better visualisation:

struct ContentView: View {
    @State var date = Date()

    var body: some View {
        VStack {
            Text("\(date.description)")
                .id("DateLabel" + date.description)
                .transition(.slide)
                .animation(.easeInOut(duration: 5))

            Button(action: { date.addTimeInterval(24*60*60) }) {
                Text("Click")
            }
        }
    }
}

Result, it's working as expected, the old label is animating out and new one is animating in:

Result1

But as soon as I wrap it inside UIHostingController:

struct ContentView: View {
    @State var date = Date()

    var body: some View {
        AnyHostingView {
            VStack {
                Text("\(date.description)")
                    .id("DateLabel" + date.description)
                    .transition(.slide)
                    .animation(.easeInOut(duration: 5))

                Button(action: { date.addTimeInterval(24*60*60) }) {
                    Text("Click")
                }
            }
        }
    }
}

struct AnyHostingView<Content: View>: UIViewControllerRepresentable {
    typealias UIViewControllerType = UIHostingController<Content>
    let content: Content

    init(content: () -> Content) {
        self.content = content()
    }

    func makeUIViewController(context: Context) -> UIHostingController<Content> {
        let vc = UIHostingController(rootView: content)
        return vc
    }

    func updateUIViewController(_ uiViewController: UIHostingController<Content>, context: Context) {
        uiViewController.rootView = content
    }
}

Result, the new label is not animated in, rather it's just inserted into it's final position, while the old label is animating out:

enter image description here

I have more complex hosting controller but this demonstrates the issue. Am I doing something wrong with the way I update the hosting controller view, or is this a bug in SwiftUI, or something else?

ios coder
  • 1
  • 4
  • 31
  • 91
muvaaa
  • 450
  • 4
  • 12

1 Answers1

2

State do not functioning well between different hosting controllers (it is not clear if this is limitation or bug, just empirical observation).

The solution is embed dependent state inside hosting view. Tested with Xcode 12.1 / iOS 14.1.

struct ContentView: View {
    var body: some View {
        AnyHostingView {
            InternalView()
        }
    }
}

struct InternalView: View {
    @State private var date = Date()   // keep relative state inside
    var body: some View {
        VStack {
             Text("\(date.description)")
                  .id("DateLabel" + date.description)
                  .transition(.slide)
                  .animation(.easeInOut(duration: 5))

             Button(action: { date.addTimeInterval(24*60*60) }) {
                  Text("Click")
             }
        }
    }
}

Note: you can also experiment with ObservableObject/ObservedObject based view model - that pattern has different life cycle.

Asperi
  • 228,894
  • 20
  • 464
  • 690
  • This is correct! But it's not an option to keep the state in the view for me as it's coming from the outside, not like in the example. I will play around with ObservableObject tho! Thanks! – muvaaa Nov 28 '20 at 16:49