大家好,我正在尝试设置偏移限制,以便
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() })
)
}
}
我认为有3个问题。
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)
}
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)
}
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