1

Using the full, 2960x1440 wallpapers with LazyVGrid is not an option, as it leads to unsatisfactory performance, even on an iPhone 12 Pro.

All assets are stored in the asset catalog. These are the Models:

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

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

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

The gallery is a simple two column grid:

import SwiftUI

struct GalleryView: View {
    @State private var fullScreen = false
    let columns = [
        GridItem(.flexible(), spacing: 2),
        GridItem(.flexible())]
    
    var body: some View {
        ZStack {                
            // GRID VIEW
            ScrollView(showsIndicators: false) {
                VStack {
                    LazyVGrid(columns: columns, spacing: 2) {
                        ForEach(thumbnailSet.indices) { index in
                            
                            Image(thumbnailSet[index].name)
                                .resizable()
                                .aspectRatio(contentMode: .fit)
                                .onTapGesture {(fullScreen = true)}
                        }
                    }
                }
            }
            .ignoresSafeArea()
            
            // FULL SCREEN VIEW
            if fullScreen {
                ZStack {
                    
                    // Code to show corresponding wallpaper?
                    
                    // BACK TO GRID VIEW
                    Button(action: {                            
                        (fullScreen = false)                            
                    }) {Image(systemName: "chevron.left")
                            .font(.system(size: 23))
                            .frame(width: 48, height: 44)
                    }
                    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
                }
                .zIndex(1)
            }
        }
    }
}

Is this doable? Ideas?

Thank you!

Neurythmic
  • 25
  • 5

1 Answers1

1

You can make your fullscreenan optional index and use that to switch and show the respective wallpaper image:

EDIT: now with animation

struct GalleryView: View {
    @State private var fullScreen: Int? = nil // make it an optional index
    
    @Namespace var namespace
    
    let columns = [
        GridItem(.flexible(), spacing: 2),
        GridItem(.flexible())]
    
    var body: some View {
        ZStack {
            // GRID VIEW
            ScrollView(showsIndicators: false) {
                VStack {
                    LazyVGrid(columns: columns, spacing: 2) {
                        ForEach(thumbnailSet.indices) { index in
                            
                            let fullscreenIndex = fullScreen ?? -1
                            if index == fullscreenIndex {
                                Color.white
                            } else {
                            Image(thumbnailSet[index].name)
                                .resizable()
                                .aspectRatio(1, contentMode: .fill)
                                .clipped()
                                .matchedGeometryEffect(id: index, in: namespace)
                            
                                .onTapGesture {
                                    withAnimation {
                                        fullScreen = index  // set index here
                                    }
                                }
                            }
                        }
                    }
                }
            }
            .ignoresSafeArea()
            // FULL SCREEN VIEW
            if let fullscreenIndex = fullScreen {
                ZStack {
                    
                    Color.white
                        .ignoresSafeArea()
                    
                    // show image based on set index
                    Image(wallpaperSet[fullscreenIndex].name)
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .matchedGeometryEffect(id: fullscreenIndex, in: namespace)

                    // BACK TO GRID VIEW
                    Button(action: {
                        withAnimation {
                            fullScreen = nil
                        }
                    }) {Image(systemName: "chevron.left")
                            .font(.system(size: 23))
                            .frame(width: 48, height: 44)
                    }
                    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
                }
                .zIndex(1)
            }
        }
    }
}
ChrisR
  • 9,523
  • 1
  • 8
  • 26
  • you're welcome :) would be nice to accept my answer as correct by checking the checkmark button on it - btw: by adding `matchedGeometryEffect` you can have the fullscreen animate from the thumbnail. search for it. – ChrisR Feb 24 '22 at 21:42
  • I added `matchedGeometryEffect(id: index, in: namespace)` and `matchedGeometryEffect(id: fullscreenIndex, in: namespace)` While the transition is OK, it has a weird transparency effect, showing both the thumb and the full image, at once. I also get this: **Multiple inserted views in matched geometry group Pair(first: 23, second: SwiftUI.Namespace.ID(id: 72)) have `isSource: true`, results are undefined.** – Neurythmic Feb 25 '22 at 05:04
  • `isSource: false` for any of the above solves the multiple inserted view issue but breaks the animation. – Neurythmic Feb 25 '22 at 06:18
  • ah yes: keep `isSource` true for both, and then show either the scrollview or the full image, so wrap them in an `if fullscreen == nil { show thumbnails... } else { show full image... }` – ChrisR Feb 25 '22 at 07:08
  • Doesn't work because of the ScrollView. It resets position after each full screen interaction. And adding both views inside, breaks the full screen. Trying to decipher this git: **https://swiftui-lab.com/matchedgeometryeffect-part1/** – Neurythmic Feb 25 '22 at 12:58
  • MatchedGeometryEffect effect starts with an opaque thumb and a transparent full size image, and inverts the opacity. As I see it, LazyVgrid has an internal zindex for each cell, increasing from top to bottom. The thumb transition happens in front of the above row of cells, and behind the row below it. Once tapped, is there a way to force the internal zindex of a cell? An easier solution would be to make the full size image opaque, start-to-finish. Haven't figured out how to do this yet. – Neurythmic Feb 25 '22 at 15:19
  • updated the code: the scrollview works, but I did not get the zIndex working :( – ChrisR Feb 25 '22 at 18:50
  • Fantastic! Thank you so much for your time, Chris. – Neurythmic Feb 25 '22 at 19:30
  • Chris, I'm trying to connect the grid to a TabView. It can't make it work, even after I looked at a similar answer of yours. I posted my new question here: https://stackoverflow.com/questions/71421042/how-to-connect-lazyvgrid-cells-with-the-corresponding-full-screen-images-in-a-ta I'm counting on you :)) – Neurythmic Mar 10 '22 at 08:40
  • This is your solution, the one that I checked: https://stackoverflow.com/questions/71087381/swiftui-matched-geometry-effect-not-working-with-multiple-foreachs/71306690#71306690 By the way, to make both in and out animations work, I think you need to have the grid and the Tabview on a ZStack, and increase the TabView's Zindex. Note how the animation fails if you remove the Zindex from my code. – Neurythmic Mar 10 '22 at 08:42
  • :) I answered to the new question ... – ChrisR Mar 10 '22 at 19:11