如何在SwiftUI中使用MagnificationGesture [Zoom In,Out]动态更改LazyVGrid中的GridItems?

xt0899hw  于 2023-06-21  发布在  Swift
关注(0)|答案(2)|浏览(123)

这个想法是重新创建相同的照片布局行为,就像在苹果照片库时,我可以放大和缩小1,3或5张照片在一排。我在半路上。为此,我使用MagnificationGesture(),并根据手势值更新LazyVGrid()GridItems()的数量。
请让我知道如何实现它。多谢了🙏
代码如下:

import SwiftUI

struct ContentView: View {
    let colors: [Color] = [.red, .purple, .yellow, .green, .blue, .mint, .orange]
    @State private var colums = Array(repeating: GridItem(), count: 1)
//    @GestureState var magnifyBy: CGFloat = 1.0
    @State var magnifyBy: CGFloat = 1.0
    @State var lastMagnifyBy: CGFloat = 1.0
    let minMagnifyBy = 1.0
    let maxMagnifyBy = 5.0
    
    var magnification: some Gesture {
        MagnificationGesture()
//            .updating($magnifyBy) { (currentState, pastState, trans) in
//                pastState = currentState.magnitude
//            }
            .onChanged { state in
                adjustMagnification(from: state)
                print("Current State \(state)")
            }
            .onEnded { state in
                adjustMagnification(from: state)
//                withAnimation(.spring()) {
//                    validateMagnificationLimits()
//                }
                lastMagnifyBy = 1.0

            }
    }
    
    var body: some View {
        NavigationView {
            ScrollView {
                LazyVGrid(columns: colums) {
                    ForEach(1..<101) { number in
                        colors[number % colors.count]
                            .overlay(Text("\(number)").font(.title2.bold()).foregroundColor(.white))
                            .frame(height: 100)
                    }
                }
                .scaleEffect(magnifyBy)
                .gesture(magnification)
                .navigationTitle("🧨 Grid")
                .toolbar {
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button {
                            withAnimation(.spring(response: 0.8)) {
                                colums = Array(repeating: .init(), count: colums.count == 5 ? 1 : colums.count % 5 + 2)
                            }
                        } label: {
                            Image(systemName: "square.grid.3x3")
                                .font(.title2)
                                .foregroundColor(.primary)
                        }
                    }
                }
            }
        }
    }
    
    private func adjustMagnification(from state: MagnificationGesture.Value) {
        let stepCount = Int(min(max(1, state), 5))
//        let delta = state / lastMagnifyBy
//        magnifyBy *= delta
        withAnimation(.linear) {
            colums = Array(repeating: GridItem(), count: stepCount)
        }
        lastMagnifyBy = state
    }
    
    private func getMinMagnificationAllowed() -> CGFloat {
        max(magnifyBy, minMagnifyBy)
    }
    
    private func getMaxMagnificationAllowed() -> CGFloat {
        min(magnifyBy, maxMagnifyBy)
    }
    
    private func validateMagnificationLimits() {
        magnifyBy = getMinMagnificationAllowed()
        magnifyBy = getMaxMagnificationAllowed()
    }
}
xbp102n0

xbp102n01#

给你这使用了TrackableScrollView(代码中的git链接)。我实现了一个可能的zoomStages数组(每行cols),以使它们之间的切换更容易。
dos旁边的按钮将滚动回放大中心,因此同一项目保持焦点。也许一个不透明的过渡,而不是重新安排网格。Have fun ;)

import SwiftUI
// https://github.com/maxnatchanon/trackable-scroll-view.git
import SwiftUITrackableScrollView

struct ContentView: View {
    
    let colors: [Color] = [.red, .purple, .yellow, .green, .blue, .mint, .orange]
    
    let zoomStages = [1, 3, 5, 9, 15]
    @State private var zoomStageIndex = 0
    var colums: [GridItem] { Array(repeating: GridItem(spacing: 0), count: zoomStages[zoomStageIndex]) }
    
    @State var magnifyBy: CGFloat = 1.0
    
    @State private var scrollViewOffset = CGFloat.zero // SwiftUITrackableScrollView: Content offset available to use
    
    var body: some View {
        NavigationView {
            TrackableScrollView(.vertical, showIndicators: false, contentOffset: $scrollViewOffset) {
                LazyVGrid(columns: colums, spacing: 0) {
                    ForEach(0..<500) { number in
                        colors[number % colors.count]
                            .overlay(
                                Text("\(number)").font(.title2.bold()).foregroundColor(.white)
                                    .minimumScaleFactor(0.1)
                            )
                            .aspectRatio(1, contentMode: .fit) // always squares
                            .id(number)
                    }
                }
                .scaleEffect(magnifyBy, anchor: .top)
                // offset to correct magnify "center" point
                .offset(x: 0, y: (scrollViewOffset + UIScreen.main.bounds.midY) * (1 - magnifyBy) )
                
                .toolbar {
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button {
                            withAnimation(.spring(response: 0.8)) {
                                if zoomStageIndex < zoomStages.count-1 {
                                    zoomStageIndex += 1
                                } else {
                                    zoomStageIndex = 0
                                }
                            }
                        } label: {
                            Image(systemName: "square.grid.3x3")
                                .font(.title2)
                                .foregroundColor(.primary)
                        }
                    }
                }
                .gesture(magnification)
            }
            .ignoresSafeArea()
        }
    }
    
    var magnification: some Gesture {
        MagnificationGesture()
            .onChanged { state in
                magnifyBy = state
            }
            .onEnded { state in
                
                // find predefined zoom(index) that is closest to actual pinch zoom value
                let newZoom = Double(zoomStages[zoomStageIndex]) * 1 / state
                let newZoomIndex = findClosestZoomIndex(value: newZoom)
                //                print("***", zoomStages[zoomStageIndex], state, newZoom, newZoomIndex)
                
                withAnimation(.spring(response: 0.8)) {
                    magnifyBy = 1 // reset scaleEffect
                    zoomStageIndex = newZoomIndex // set new zoom level
                }
            }
    }
    
    func findClosestZoomIndex(value: Double) -> Int {
        let distanceArray = zoomStages.map { abs(Double($0) - value) } // absolute difference between zoom stages and actual pinch zoom
        //        print("dist:", distanceArray)
        return distanceArray.indices.min(by: {distanceArray[$0] < distanceArray[$1]}) ?? 0 // return index of element that is "closest"
    }
    
}
cx6n0qe3

cx6n0qe32#

如果你想模仿照片应用程序在放大/捏时的行为,你必须考虑:
1.当前规模
1.根据方向移动到下一个布局所需的比例/缩放因子

Example project (SwiftUI without third-party libraries)

相关问题