2

I have a parent view that conditionally displays a subview:

struct HomeView: View {    
    @State var selectedView = true

    var body: some View {
        VStack {
            Picker("Selected", selection: $selectedView) {
                Text("A").tag(true)
                Text("B").tag(false)
            }.pickerStyle(SegmentedPickerStyle())
            
            if selectedView {
                SubView()
            } else {
                Text("Other")
            }
        }
    }
}

My subview contains an AsyncImage that loads from a URL:

struct SubView: View {
    private let url = URL(string: "some url here")
    var body: some View {
        AsyncImage(url: url)
    }
}

My issue is that SubView is recreated every time I switch the picker to B and then back to A. This recreates the AsyncImage and causes a reload from the URL, which is not necessary.

Is there a way to prevent SubView from being recreated here? I notice that TabView does not seem to recreate its containing views when switching between them. Is it possible to get this functionality using the structure I have?

I have tried making SubView equatable and using the .equatable() modifier on its instance as such:

if selectedView {
    SubView().equatable()
} else {
    Text("Other")
}

However, the image is still reloaded.

cjweeks
  • 267
  • 1
  • 4
  • 8
  • Does this answer your question https://stackoverflow.com/a/60483313/12299030? – Asperi Jan 29 '22 at 18:34
  • I have tried making SubView Equatable and using the modifier on its instance. However, the image is still reloaded. I have updated the question to reflect this. – cjweeks Jan 29 '22 at 19:01

3 Answers3

1

Either get rid of the if that creates a _ConditionalView and use a modifier instead, or enable the URLSession's URLCache so the image is cached in memory or disk and isn't actually downloaded again.

malhal
  • 26,330
  • 7
  • 115
  • 133
  • What modifier can I use to conditionally hide the view? I took a look at https://stackoverflow.com/questions/56490250/dynamically-hiding-view-in-swiftui but the proposed view modifier still uses a _ConditionalView. – cjweeks Jan 29 '22 at 19:28
  • This should help https://developer.apple.com/videos/play/wwdc2021/10022/ – malhal Jan 30 '22 at 02:18
  • I understand the lifecycle of views here. The SubView is essentially getting destroyed and recreated when I switch from A to B and back to A. Things like TabView and NavigationView do not seem to do this; they maintain the views even if they are not shown. Is there a way to accomplish this using my workflow? I am essentially just trying to show different persistent subviews based on a Picker. If this is not possible, can it be done in UIKit and adapted to SwiftUI? – cjweeks Jan 30 '22 at 19:18
  • 1
    You could use a TabView with a selection binding and use the picker to set the selected tab. Or implement a UIVIewController that hosts and caches 2 SwiftUI views and can switch between them the same as the TabView does using the UITabController – malhal Jan 30 '22 at 22:41
0

My only workaround that is not pretty:

MyView().opacity(shouldHidden? 0 : 1)
Yanpei Shi
  • 11
  • 3
0

So I found my previous answer using opacity is bad because the hidden view remains in the view hierarchy which I think can be a performance issue as hidden views get complex. So I came up with this other approach where you can just put a SwiftUI View into UIViewController and use UIViewControllerRepresentable to present it in SwiftUI code. This way you get to save the View's states while also removing it from view hierarchy. Here's a minimal example, you can scroll the scroll view and click the button to hide it and show it again, and the scroll view stays in the same offset without getting recreated. Hope it helps.

import SwiftUI

let myView = MyView()
let hostingController = UIHostingController(rootView: myView)


struct MyView: View {
    var body: some View {
        ScrollView {
            LazyVStack(alignment: .leading) {
                ForEach(0 ... 100, id: \.self) {i in
                    Text("\(i)")
                }
            }
        }
        
    }
}

struct VC_Wrapper: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> UIViewController {
        return hostingController
    }
    
    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
    }
    typealias UIViewControllerType = UIViewController
}

struct ContentView: View {
    @State var isShow = true {
        didSet {
            hostingController.view.isHidden = !isShow
        }
    }
    var body: some View {
        ZStack {
            VC_Wrapper()
            Button("show/hide") {
                isShow.toggle()
            }.background(.red)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
Yanpei Shi
  • 11
  • 3