Home > Software design >  How to connect LazyVGrid cells with the corresponding full screen images in a TabView (Xcode 13, iOS
How to connect LazyVGrid cells with the corresponding full screen images in a TabView (Xcode 13, iOS

Time:03-11

I'm having a hard time matching the correct grid cell when zooming out from the corresponding tab.

Just starting to learn and I should definitely go through a few more tutorials. If you want to lend a hand here, thank you in advance.

This is the code:

Models

struct Thumbnail: Identifiable {
    let id = UUID()
    var name: String
}

struct Wallpaper: Identifiable {
    var id = UUID()
    var name: String
}

let thumbnailSet = (1...50).map { Thumbnail(name: "iridisfera-thumbnail-\($0)") }
let wallpaperSet = (1...50).map { Wallpaper(name: "iridisfera-wallpaper-\($0)") }

Gallery (I removed my selectedTab experiments so you can directly insert your code)

struct GalleryView: View {
    @Namespace var namespace
    @State private var fullScreen: Int? = nil
    @State private var selectedTab = 0
    
    let columns = [GridItem(.flexible(), spacing: 2), GridItem(.flexible())]
    
    var body: some View {
        ZStack {
            // MARK: GRID VIEW
            ScrollView(showsIndicators: false) {
                LazyVGrid(columns: columns, spacing: 2) {
                    ForEach(thumbnailSet.indices) { index in
                        
                        let fullscreenIndex = fullScreen
                        if index == fullscreenIndex {
                            Color.clear
                        } else {
                            Image(thumbnailSet[index].name)
                                .resizable()
                                .aspectRatio(0.5, contentMode: .fit)
                                .matchedGeometryEffect(id: index, in: namespace)
                                .onTapGesture {
                                    withAnimation(.interpolatingSpring(mass: 0.2, stiffness: 34, damping: 4)) {fullScreen = index}
                                }
                        }
                    }
                }
            }
            .ignoresSafeArea()
            
            // MARK: FULL SCREEN VIEW
            if let fullscreenIndex = fullScreen {
                TabView(selection: $selectedTab) {
                    ForEach(wallpaperSet.indices) { index in
                        
                        Image(wallpaperSet[index].name)
                            .resizable()
                            .ignoresSafeArea()
                            .scaledToFill()
                    }
                }
                .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
                .matchedGeometryEffect(id: fullscreenIndex, in: namespace)
                .ignoresSafeArea()
                .zIndex(1)
                .onTapGesture {
                    withAnimation(.interpolatingSpring(mass: 0.1, stiffness: 28, damping: 4)) {fullScreen = nil}
                }
            }
        }
    }
}

CodePudding user response:

This code shows the general approach. The TabView has to be in a separate struct, so it can be initialized with the already selected tab. The images in TabView need to have .tag() to identify them for the selection.
It does not work perfectly yet, as your images are not identifiable, but it should give you the direction. Working with the indices alone is unsafe, so you should put them in an Identifiable struct and select by the id.

struct GalleryView: View {
    
    @Namespace var namespace
    @State private var fullScreen: Int? = nil
    
    let columns = [GridItem(.flexible(), spacing: 2), GridItem(.flexible())]
    
    var body: some View {
        ZStack {
            // MARK: GRID VIEW
            ScrollView(showsIndicators: false) {
                LazyVGrid(columns: columns, spacing: 2) {
                    ForEach(thumbnailSet.indices) { index in
                        
                        if index == fullScreen {
                            Color.clear
                        } else {
                            Image(thumbnailSet[index].name)
                                .resizable()
                                .aspectRatio(1, contentMode: .fill)
                                .matchedGeometryEffect(id: index, in: namespace)
                                .onTapGesture {
                                    withAnimation {
                                        fullScreen = index
                                    }
                                }
                        }
                    }
                }
            }
            .ignoresSafeArea()
            
            // MARK: FULL SCREEN VIEW
            if fullScreen != nil {
                FullscreenTabView(selectedImage: $fullScreen, ns: namespace)
            }
        }
    }
}


struct FullscreenTabView: View {
    
    @Binding var selectedImage: Int?
    var ns: Namespace.ID
    
    init(selectedImage: Binding<Int?>, ns: Namespace.ID) {
        self._selectedImage = selectedImage
        self.ns = ns
        // initialize selctedTab to selectedImage
        self._selectedTab = State(initialValue: selectedImage.wrappedValue ?? 0)
    }
    
    @State private var selectedTab: Int
    
    var body: some View {
        TabView(selection: $selectedTab) {
            
            ForEach(wallpaperSet.indices) { index in
                
                Image(wallpaperSet[index].name)
                    .resizable()
                    .tag(index) // << the images in TabView need tags to identify
                    .ignoresSafeArea()

                    .matchedGeometryEffect(id: index == selectedTab ? selectedTab : 0,
                                           in: ns, isSource: true)
                                
                    .onTapGesture {
                        withAnimation {
                            selectedImage = nil
                        }
                    }
            }
        }
        .ignoresSafeArea()
        .tabViewStyle(.page(indexDisplayMode: .never) )
    }
}
  • Related