SwiftUI LazyVStack 重叠图像

Posted

技术标签:

【中文标题】SwiftUI LazyVStack 重叠图像【英文标题】:SwiftUI LazyVStack overlapping images 【发布时间】:2021-06-20 02:23:32 【问题描述】:

我试图在嵌入滚动视图的 LazyVStack 中显示两列图像,但是第二行图像与上面的行部分重叠。我不确定这是 LazyVStack 本身的问题还是 Photo.swift 视图的问题。

输出看起来像这样

两个视图文件

ContentView.swift

struct ContentView: View 
    @State private var image: Image?
    @State private var showingCustomCamera = false
    @State private var inputImage: UIImage?
    @State private var photos: [UIImage] = []
    
    func addImageToArray() 
        guard let inputImage = inputImage else  return 
        image = Image(uiImage: inputImage)
        
        let ciImage = CIImage(cgImage: inputImage.cgImage!)

        let options = [CIDetectorAccuracy: CIDetectorAccuracyHigh]
        let faceDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: options)!

        let faces = faceDetector.features(in: ciImage)

        if let face = faces.first as? CIFaceFeature 
            print("Found face at \(face.bounds)")

            print(face.faceAngle)
            print(face.hasSmile)
            print(face.leftEyeClosed)
            print(face.rightEyeClosed)
            
            if face.leftEyeClosed 
                print("Left Eye Closed \(face.leftEyePosition)")
            

            if face.rightEyeClosed 
                print("Right Eye Closed \(face.rightEyePosition)")
            

            if face.hasSmile 
                print("Person is smiling \(face.mouthPosition)")
            
        
        
        photos.append(inputImage)
    
    
    let columns = [
        GridItem(.flexible(), spacing: 20),
        GridItem(.flexible(), spacing: 20)
    ]
    
    var body: some View 
        NavigationView 
            VStack
                ScrollView 
                    LazyVGrid(columns: columns, spacing: 20) 
                        AddPhoto(showCamera: $showingCustomCamera)
                        
                        ForEach(photos, id: \.self)  photo in
                            PassportPhoto(img: photo)
                        
                    
                    .padding()
                
                
                HStack 
                    Button(action: 
                        //
                    , label: 
                        Image(systemName: "printer.fill.and.paper.fill")
                        Text("Print")
                    )
                    .padding()
                    .foregroundColor(.primary)
                    
                    Button(action: 
                        //
                    , label: 
                        Image(systemName: "externaldrive.fill.badge.icloud")
                        Text("Digital Upload")
                    )
                    .padding()
                    .foregroundColor(.primary)
                
            
            .sheet(isPresented: $showingCustomCamera, onDismiss: addImageToArray) 
                CustomCameraView(image: self.$inputImage)
            
            .navigationTitle("Add Photos")
        
    

照片.swift

struct Photo: View 
    var img: UIImage
    @State private var overlay: Bool = false
    
    var body: some View 
        GeometryReader  geometry in
            VStack 
                ZStack(alignment: .top) 
                    Image(uiImage: img)
                        .resizable()
                        .aspectRatio(contentMode: .fill)
                        .frame(width: geometry.size.width, height: geometry.size.width * 1.29, alignment: .top)
                        .clipped()
                        .cornerRadius(10)
                        .onTapGesture 
                            self.overlay.toggle()
                        
                    
                    if overlay 
                        Template()
                    
                
            
        
    

有人知道吗?我觉得我错过了一些明显的东西。

CustomCameraView.swift(根据要求)

import SwiftUI
import AVFoundation
 
struct CustomCameraView: View 
    @Binding var image: UIImage?
    @State var didTapCapture: Bool = false
    @Environment(\.presentationMode) var presentationMode
    
    var body: some View 
        VStack(alignment: .center) 
            CustomCameraRepresentable(image: self.$image, didTapCapture: $didTapCapture)
                .overlay(Template(),alignment: .center)
                .overlay(
                    CaptureButtonView().onTapGesture 
                        self.didTapCapture = true
                    
                    , alignment: .bottom)
                .overlay(
                    Button(action: 
                        presentationMode.wrappedValue.dismiss()
                    , label: 
                        Image(systemName: "multiply")
                            .scaleEffect(2)
                            .padding(20)
                            .onTapGesture 
                                presentationMode.wrappedValue.dismiss()
                            
                    )
                    .foregroundColor(.white)
                    .padding()
                    , alignment: .topTrailing)
        
    
    



struct CustomCameraRepresentable: UIViewControllerRepresentable 
    
    @Environment(\.presentationMode) var presentationMode
    @Binding var image: UIImage?
    @Binding var didTapCapture: Bool
    
    func makeUIViewController(context: Context) -> CustomCameraController 
        let controller = CustomCameraController()
        controller.delegate = context.coordinator
        return controller
    
    
    func updateUIViewController(_ cameraViewController: CustomCameraController, context: Context) 
        
        if(self.didTapCapture) 
            cameraViewController.didTapRecord()
        
    
    func makeCoordinator() -> Coordinator 
        Coordinator(self)
    
    
    class Coordinator: NSObject, UINavigationControllerDelegate, AVCapturePhotoCaptureDelegate 
        let parent: CustomCameraRepresentable
        
        init(_ parent: CustomCameraRepresentable) 
            self.parent = parent
        
        
        func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) 
            
            parent.didTapCapture = false
            
            if let imageData = photo.fileDataRepresentation() 
                parent.image = UIImage(data: imageData)
            
            parent.presentationMode.wrappedValue.dismiss()
        
    


class CustomCameraController: UIViewController 
    
    var image: UIImage?
    
    var captureSession = AVCaptureSession()
    var backCamera: AVCaptureDevice?
    var frontCamera: AVCaptureDevice?
    var currentCamera: AVCaptureDevice?
    var photoOutput: AVCapturePhotoOutput?
    var cameraPreviewLayer: AVCaptureVideoPreviewLayer?
    
    //DELEGATE
    var delegate: AVCapturePhotoCaptureDelegate?
    
    func didTapRecord() 
        
        let settings = AVCapturePhotoSettings()
        photoOutput?.capturePhoto(with: settings, delegate: delegate!)
        
    
    
    override func viewDidLoad() 
        super.viewDidLoad()
        setup()
    
    func setup() 
        setupCaptureSession()
        setupDevice()
        setupInputOutput()
        setupPreviewLayer()
        startRunningCaptureSession()
    
    func setupCaptureSession() 
        captureSession.sessionPreset = AVCaptureSession.Preset.photo
    
    
    func setupDevice() 
        let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera],
                                                                      mediaType: AVMediaType.video,
                                                                      position: AVCaptureDevice.Position.unspecified)
        for device in deviceDiscoverySession.devices 
            
            switch device.position 
            case AVCaptureDevice.Position.front:
                self.frontCamera = device
            case AVCaptureDevice.Position.back:
                self.backCamera = device
            default:
                break
            
        
        
        self.currentCamera = self.backCamera
    
    
    
    func setupInputOutput() 
        do 
            
            let captureDeviceInput = try AVCaptureDeviceInput(device: currentCamera!)
            captureSession.addInput(captureDeviceInput)
            photoOutput = AVCapturePhotoOutput()
            photoOutput?.setPreparedPhotoSettingsArray([AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])], completionHandler: nil)
            captureSession.addOutput(photoOutput!)
            
         catch 
            print(error)
        
        
    
    func setupPreviewLayer()
    
        let rect = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.width * 1.29)
        
        self.cameraPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        self.cameraPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
        self.cameraPreviewLayer?.connection?.videoOrientation = AVCaptureVideoOrientation.portrait
        self.cameraPreviewLayer?.frame = rect
        self.view.layer.insertSublayer(cameraPreviewLayer!, at: 0)
        
    
    func startRunningCaptureSession()
        captureSession.startRunning()
    



struct CaptureButtonView: View 
    @State private var animationAmount: CGFloat = 1
    var body: some View 
        Image(systemName: "camera").font(.largeTitle)
            .padding(30)
            .background(Color.red)
            .foregroundColor(.white)
            .clipShape(Circle())
            .overlay(
                Circle()
                    .stroke(Color.red)
                    .scaleEffect(animationAmount)
                    .opacity(Double(2 - animationAmount))
                    .animation(Animation.easeOut(duration: 1)
                        .repeatForever(autoreverses: false))
        )
            .padding(.bottom)
            .onAppear
            
                self.animationAmount = 2
        
    

【问题讨论】:

你能显示 CustomCameraView 的代码吗? @aheze 我已经添加了 可能是“网格”没有得到正确的“AddPhoto(...)”大小。 “AddPhoto(showCamera: $showingCustomCamera)”是否返回有界大小的视图?例如,尝试类似于 PassportPhoto/Photo 尺寸的东西:“frame(maxWidth: geometry.size.width, maxHeight: geometry.size.width * 1.29)”" @workingdog 即使我完全删除 AddPhoto 并让它显示一组图像,它的行为也是一样的。 【参考方案1】:

你不应该在ScrollView 中使用GeometryReader,它会给你造成各种各样的混乱。而是在 VStack 下方的顶层定义它,并将 proxy 向下传递到 Photo 视图以设置 Frame。

检查下面的代码-:

import SwiftUI


struct Test1: View 
    @State private var image: Image?
    @State private var showingCustomCamera = false
    @State private var inputImage: UIImage?
    @State private var photos: [UIImage] = []
    
    func addImageToArray() 
        guard let inputImage = inputImage else  return 
        image = Image(uiImage: inputImage)
        
        let ciImage = CIImage(cgImage: inputImage.cgImage!)
        
        let options = [CIDetectorAccuracy: CIDetectorAccuracyHigh]
        let faceDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: options)!
        
        let faces = faceDetector.features(in: ciImage)
        
        if let face = faces.first as? CIFaceFeature 
            print("Found face at \(face.bounds)")
            
            print(face.faceAngle)
            print(face.hasSmile)
            print(face.leftEyeClosed)
            print(face.rightEyeClosed)
            
            if face.leftEyeClosed 
                print("Left Eye Closed \(face.leftEyePosition)")
            
            
            if face.rightEyeClosed 
                print("Right Eye Closed \(face.rightEyePosition)")
            
            
            if face.hasSmile 
                print("Person is smiling \(face.mouthPosition)")
            
        
        
        photos.append(inputImage)
    
    
    let columns =
        [GridItem(.flexible(),spacing: 20),
         GridItem(.flexible(),spacing: 20)]
    
    
    var body: some View 
        NavigationView 
            VStack
                GeometryReader  geometry in
                    ScrollView 
                        LazyVGrid(columns: columns, spacing: 20) 
                            // AddPhoto(showCamera: $showingCustomCamera) // Uncomment in your case
                            
                            
                            ForEach(0..<50, id: \.self)  photo in
                                Photo(img: "ABC", proxy: geometry) // Pass photo as you were doing
                                
                            
                        
                        .padding()
                    
                
                
                HStack 
                    Button(action: 
                        //
                    , label: 
                        Image(systemName: "printer.fill.and.paper.fill")
                        Text("Print")
                    )
                    .padding()
                    .foregroundColor(.primary)
                    
                    Button(action: 
                        //
                    , label: 
                        Image(systemName: "externaldrive.fill.badge.icloud")
                        Text("Digital Upload")
                    )
                    .padding()
                    .foregroundColor(.primary)
                
            
            .sheet(isPresented: $showingCustomCamera, onDismiss: addImageToArray) 
                // CustomCameraView(image: self.$inputImage)
            
            .navigationTitle("Add Photos")
        
    


struct Photo: View 
    var img: String
    var proxy:GeometryProxy
    @State private var overlay: Bool = false
    
    var body: some View 
        //  GeometryReader  geometry in
        VStack 
            ZStack(alignment: .top) 
                Image(img)
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    // .frame(width: 170, height: 200)
                    .frame(width: proxy.size.width * 0.4, height: proxy.size.width * 0.5, alignment: .top)
                    .clipped()
                    .cornerRadius(10)
                    .onTapGesture 
                        self.overlay.toggle()
                    
                
                if overlay 
                    // Template()
                
            
        
        //
    
 

【讨论】:

以上是关于SwiftUI LazyVStack 重叠图像的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI 匹配几何 + LazyVStack = 崩溃

在 SwiftUI 1 中使用 LazyVStack

SwiftUI 如何创建选择列表的 LazyVStack

LazyVStack 和 SwiftUI 的性能不佳问题

navigationBarTitle 与 SwiftUI 中的列表标题重叠

将图像的可点击部分剪辑为 NavigationLink (SwiftUI)