计算掩模边界的偏移限制

问题描述 投票:0回答:1

大家好,我正在尝试设置偏移限制,以便

dragGesture
期间的图像永远不会超过蒙版的轮廓...

感谢 Benzy Neez 的帮助,我获得了用于图像的

scaleOffset
值,以便原始偏移考虑到图像大小的变化,但此时我无法再制作限制工作..

我肯定做了一些非常愚蠢的错误,但我找不到错误

我给你看代码

@MainActor
protocol PlatformView: View {
    var horizontalSizeClass: UserInterfaceSizeClass? { get }
    var verticalSizeClass: UserInterfaceSizeClass? { get }
    var isCompact: Bool { get }
    var isRegular: Bool { get }
}

extension PlatformView {
    var isCompact: Bool { horizontalSizeClass == .compact || verticalSizeClass == .compact }
    var isRegular: Bool { horizontalSizeClass == .regular && verticalSizeClass == .regular }
}

public enum Mask {
    case circle, square, rectangle(aspectRatio: AspectRatio)
    
    var shape: AnyShape {
        switch self {
        case .circle: return AnyShape(.circle)
        case .square, .rectangle : return AnyShape(.rect)
        }
    }
    
    func size(relativeTo size: CGSize) -> CGSize {
        let isLandscape = size.width > size.height
        let reference = isLandscape ? size.height : size
            .width

        switch self {
        case .circle, .square :
            return .init(width: reference, height: reference)
        
        case .rectangle(let aspectRatio):
            return .init(width: reference, height: reference * aspectRatio.ratio)
        }
    }

    public enum AspectRatio {
        case ar4_3, ar16_9
        var ratio: CGFloat {
            switch self {
            case .ar4_3: return 4/3
            case .ar16_9: return 16/9
            }
        }
    }
}

@Observable public class CroxioManagaer {
     
    var offset: CGSize = .zero
    var lastOffset: CGSize = .zero
    var photoSize: CGSize = .zero
    var maskSize: CGSize = .zero
    
    func currentOffset(translation: CGSize) -> CGSize  {
        let currentOffset: CGSize = .init(width: translation.width / photoSize.width, height: translation.height / photoSize.height)
        return lastOffset + currentOffset
       
    }
    
    func limitedOffset(translation: CGSize) {
        let ratioX = photoSize.width / maskSize.width
        let ratioY = photoSize.height / maskSize.height
        let maxX = ((maskSize.width - photoSize.width) / 2) / ratioX
        let maxY = ((maskSize.height - photoSize.height) / 2) / ratioY
        let currentOffset = currentOffset(translation: translation)
        let width = max(-maxX, min(maxX, currentOffset.width))
        let height = max(-maxY, min(maxY, currentOffset.height))
        offset = .init(width: width, height: height)
    }
    
    func saveOffset() {
        lastOffset = offset
    }
}

extension UIImage {
    private func aspectRatio(_ size: CGSize = .zero) -> CGFloat {
        return size == .zero ? self.size.width / self.size.height : size.width / size.height
    }
        
    func adaptPhotoSize(to size: CGSize) -> CGSize {
        let photoAspectRatio = aspectRatio()
        let maskAspectRatio = aspectRatio(size)
        
        if photoAspectRatio > maskAspectRatio {
            // La foto è più larga rispetto alla maschera: scala per altezza
            let width = size.height * photoAspectRatio
            return .init(width: width, height: size.height)
        } else {
            // La foto è più alta rispetto alla maschera: scala per larghezza
            let height = size.width * photoAspectRatio
            return .init(width: size.width, height: height)
        }
    }
}

extension CGSize {
    static func + (lhs: CGSize, rhs: CGSize) -> CGSize {
        .init(width: lhs.width + rhs.width, height: lhs.height + rhs.height)
    }
}

public struct Croxio: PlatformView {
    var selectedPhoto: UIImage
    let prefersShape: Mask
    
    public init(selectedPhoto: UIImage, prefersShape: Mask = .circle) {
        self.selectedPhoto = selectedPhoto
        self.prefersShape = prefersShape
    }
    
    @Environment(\.horizontalSizeClass) var horizontalSizeClass
    @Environment(\.verticalSizeClass) var verticalSizeClass
    
    @State private var manager = CroxioManagaer()
    
    private var scaledOffset: CGSize {
        CGSize(
            width: manager.offset.width * manager.photoSize.width,
            height: manager.offset.height * manager.photoSize.height
        )
    }
    
    public var body: some View {
        ZStack {
            Image(uiImage: selectedPhoto)
                .resizable()
                .scaledToFill()
                .frame(manager.photoSize)
                .offset(scaledOffset)

            Color.primary.opacity(0.5)
                .mask {
                    Rectangle()
                        .overlay {
                            prefersShape.shape
                                .frame(manager.maskSize)
                                .blendMode(.destinationOut)
                        }
                }
        }
        .onGeometryChange(for: CGSize.self, of: { proxy in
            proxy.size
        }, action: { newValue in
            manager.maskSize = prefersShape.size(relativeTo: newValue * 0.7)
            manager.photoSize = selectedPhoto.adaptPhotoSize(to: manager.maskSize)
        })
        .ignoresSafeArea()
        .gesture(
            DragGesture()
                .onChanged({ value in
                    manager.limitedOffset(translation: value.translation)
                })
                .onEnded({ _ in manager.saveOffset() })
        )
    }
}
ios swift swiftui offset cgsize
1个回答
0
投票

我认为有3个问题。

  1. 在函数
    adaptPhotoSize
    中,计算应该使用除法而不是乘法:
if photoAspectRatio > maskAspectRatio {
    // La foto è più larga rispetto alla maschera: scala per altezza
    let width = size.height / photoAspectRatio // 👈 here
    return .init(width: width, height: size.height)
} else {
    // La foto è più alta rispetto alla maschera: scala per larghezza
    let height = size.width / photoAspectRatio // 👈 and here
    return .init(width: size.width, height: height)
}
  1. 功能
    limitedOffset
    无法正常工作。试试这样:
func limitedOffset(translation: CGSize) {
    let maxX = (photoSize.width - maskSize.width) / (2 * photoSize.width)
    let maxY = (photoSize.height - maskSize.height) / (2 * photoSize.height)
    let currentOffset = currentOffset(translation: translation)
    let width = max(-maxX, min(maxX, currentOffset.width))
    let height = max(-maxY, min(maxY, currentOffset.height))
    offset = .init(width: width, height: height)
}
  1. 照片缩放可能会影响
    ZStack
    尺寸

您之前没有注意到照片尺寸错误的原因是因为您正在缩放它以填充显示区域。您不需要使用

.scaledToFill
scaledToFit
,因为您正在以精确尺寸在照片上设置框架。

但是,还有一个问题。

照片包含在

ZStack
内。如果照片超出显示范围,则会导致
ZStack
增大,从而触发
.onGeometryChange
。这会改变照片的大小,从而改变显示的大小......这会导致连续循环。例如,当在宽屏幕上显示高图像时,例如在横向的 iPad 上,就会发生这种情况。

要解决此问题,我建议在 ZStack

background
中显示照片。然后将
Color.clear
添加到
ZStack
,以确保它填满整个屏幕。这样,照片的大小就不会影响
ZStack
的大小,从而解决了循环问题:

ZStack {
    Color.clear

    Color.primary.opacity(0.5)
        .mask {
            // ...
        }
}
.background {
    Image(uiImage: selectedPhoto)
        .resizable()
        .frame(width: manager.photoSize.width, height: manager.photoSize.height)
        .offset(scaledOffset)
}
// + other modifiers, as before
© www.soinside.com 2019 - 2024. All rights reserved.