[英]How To Scale The Contents Of A UIView To Fit A Destination Rectangle Whilst Maintaining The Aspect Ratio?
我试图解决一个没有成功的问题,希望有人能提供帮助。
我一直在寻找类似的帖子,但找不到任何可以解决我问题的东西。
我的场景如下:我有一个UIView
可以放置许多其他UIView
。 这些可以使用手势识别器移动、缩放和旋转(这里没有问题)。 用户能够更改主视图(画布)的纵横比,我的问题是尝试缩放画布的内容以适应新的目标大小。
有许多具有相似主题的帖子,例如:
但是这些并没有解决比率的多次变化。
我的方法:
当我更改画布的纵横比时,我使用AVFoundation
来计算画布的子视图应该适合的宽高比拟合矩形:
let sourceRectangleSize = canvas.frame.size
canvas.setAspect(aspect, screenSize: editorLayoutGuide.layoutFrame.size)
view.layoutIfNeeded()
let destinationRectangleSize = canvas.frame.size
let aspectFittedFrame = AVMakeRect(aspectRatio:sourceRectangleSize, insideRect: CGRect(origin: .zero, size: destinationRectangleSize))
ratioVisualizer.frame = aspectFittedFrame
红框只是为了可视化 Aspect Fitted Rectangle。 正如您所看到的,虽然方面适合的矩形是正确的,但对象的缩放不起作用。 当我将缩放和旋转应用于子视图(CanvasElement)时尤其如此。
我缩放对象的逻辑显然是错误的:
@objc
private func setRatio(_ control: UISegmentedControl) {
guard let aspect = Aspect(rawValue: control.selectedSegmentIndex) else { return }
let sourceRectangleSize = canvas.frame.size
canvas.setAspect(aspect, screenSize: editorLayoutGuide.layoutFrame.size)
view.layoutIfNeeded()
let destinationRectangleSize = canvas.frame.size
let aspectFittedFrame = AVMakeRect(aspectRatio:sourceRectangleSize, insideRect: CGRect(origin: .zero, size: destinationRectangleSize))
ratioVisualizer.frame = aspectFittedFrame
let scale = min(aspectFittedFrame.size.width/canvas.frame.width, aspectFittedFrame.size.height/canvas.frame.height)
for case let canvasElement as CanvasElement in canvas.subviews {
canvasElement.frame.size = CGSize(
width: canvasElement.baseFrame.width * scale,
height: canvasElement.baseFrame.height * scale
)
canvasElement.frame.origin = CGPoint(
x: aspectFittedFrame.origin.x + canvasElement.baseFrame.origin.x * scale,
y: aspectFittedFrame.origin.y + canvasElement.baseFrame.origin.y * scale
)
}
}
如果这有帮助,我也会附上 CanvasElement 类:
final class CanvasElement: UIView {
var rotation: CGFloat = 0
var baseFrame: CGRect = .zero
var id: String = UUID().uuidString
// MARK: - Initialization
override init(frame: CGRect) {
super.init(frame: frame)
storeState()
setupGesture()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
// MARK: - Gesture Setup
private func setupGesture() {
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panGesture(_:)))
let pinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(pinchGesture(_:)))
let rotateGestureRecognizer = UIRotationGestureRecognizer(target: self, action: #selector(rotateGesture(_:)))
addGestureRecognizer(panGestureRecognizer)
addGestureRecognizer(pinchGestureRecognizer)
addGestureRecognizer(rotateGestureRecognizer)
}
// MARK: - Touches
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
moveToFront()
}
//MARK: - Gestures
@objc
private func panGesture(_ sender: UIPanGestureRecognizer) {
let move = sender.translation(in: self)
transform = transform.concatenating(.init(translationX: move.x, y: move.y))
sender.setTranslation(CGPoint.zero, in: self)
storeState()
}
@objc
private func pinchGesture(_ sender: UIPinchGestureRecognizer) {
transform = transform.scaledBy(x: sender.scale, y: sender.scale)
sender.scale = 1
storeState()
}
@objc
private func rotateGesture(_ sender: UIRotationGestureRecognizer) {
rotation += sender.rotation
transform = transform.rotated(by: sender.rotation)
sender.rotation = 0
storeState()
}
// MARK: - Miscelaneous
func moveToFront() {
superview?.bringSubviewToFront(self)
}
public func rotated(by degrees: CGFloat) {
transform = transform.rotated(by: degrees)
rotation += degrees
}
func storeState() {
print("""
Element Frame = \(frame)
Element Bounds = \(bounds)
Element Center = \(center)
""")
baseFrame = frame
}
}
任何帮助或建议,方法,以及一些实际的例子都会很棒。 我不希望任何人提供完整的源代码,而是我可以用作基础的东西。
感谢您花时间阅读我的问题。
这里有一些想法和发现,同时玩这个
1. 是否使用了正确的比例因子?
您使用的缩放比例有点自定义,不能直接与只有 1 个比例因子(如 2 或 3)的示例进行比较。但是,您的比例因子有 2 个维度,但我看到您对此进行了补偿以获得最小的宽度和高度缩放:
let scale = min(aspectFittedFrame.size.width / canvas.frame.width,
aspectFittedFrame.size.height / canvas.frame.height)
在我看来,我认为这不是正确的比例因子。 对我来说,这将新的aspectFittedFrame
与新的canvas frame
进行了比较
实际上我认为正确的缩放因子是将新的aspectFittedFrame
与以前的canvas frame
进行比较
let scale
= min(aspectFittedFrame.size.width / sourceRectangleSize.width,
aspectFittedFrame.size.height / sourceRectangleSize.height)
2. 量表是否适用于正确的值?
如果您注意到,从 1:1 到 16:9 的第一个顺序效果很好。 但是在那之后它似乎不起作用,我相信问题就在这里:
for case let canvasElement as CanvasElement in strongSelf.canvas.subviews
{
canvasElement.frame.size = CGSize(
width: canvasElement.baseFrame.width * scale,
height: canvasElement.baseFrame.height * scale
)
canvasElement.frame.origin = CGPoint(
x: aspectFittedFrame.origin.x
+ canvasElement.baseFrame.origin.x * scale,
y: aspectFittedFrame.origin.y
+ canvasElement.baseFrame.origin.y * scale
)
}
第一次,缩放效果很好,因为画布和画布元素正在同步缩放或正确映射:
但是,如果您超出此范围,因为您总是根据基本值进行缩放,因此您的纵横比框架和画布元素不同步
所以在 1:1 -> 16:9 -> 3:2 的例子中
所以我觉得看到红色视口中的值,您需要基于前一个视图而不是基础视图应用相同的连续缩放。
只是为了立即快速修复,我通过调用canvasElement.storeState()
在每次更改画布大小后更新画布元素的基本值:
for case let canvasElement as CanvasElement in strongSelf.canvas.subviews
{
canvasElement.frame.size = CGSize(
width: canvasElement.baseFrame.width * scale,
height: canvasElement.baseFrame.height * scale
)
canvasElement.frame.origin = CGPoint(
x: aspectFittedFrame.origin.x
+ canvasElement.baseFrame.origin.x * scale,
y: aspectFittedFrame.origin.y
+ canvasElement.baseFrame.origin.y * scale
)
// I added this
canvasElement.storeState()
}
结果也许更接近你想要的?
最后的想法
虽然这可能会解决您的问题,但您会注意到不可能恢复到原始状态,因为在每一步都应用了转换。
一种解决方案可能是存储映射到特定视口纵横比的当前值,并为其他值计算正确的大小,以便如果您需要返回原始值,您可以这样做。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.