我需要在视图中显示一些异步加载的图像,它们的大小可以从非常小(例如 20x20)到巨大(2000+)。
获取大图像以适应视图非常简单:
AsyncImage(
url: image.thumb,
content: { image in
image.resizable()
.aspectRatio(contentMode: .fit)
},
placeholder: {
ProgressView()
}
)
...但这也会拉伸较小的图像以适应视图。
我该如何表达“请以原始尺寸显示此图像,除非大于视图,在这种情况下将其夹紧”?(在 CSS 中,类似于
img { maxWidth: 100%; height: auto; }
)
SwiftUI 缩放图像仅当它更大时的答案显示了一种防止常规
Image
在缩放以适应时放大的方法(这是我的答案)。这个想法是使用 ViewThatFits
,首先提供未缩放的图像,然后提供缩放以适合的图像。
对于
AsyncImage
,可以在内容闭包内使用 ViewThatFits
。但是,我发现只有在 ViewThatFits
上设置最大宽度和高度时它才有效。另外,我发现即使将 .fixedSize()
应用于 ViewThatFits
和 AsyncImage
,框架尺寸仍然大于所需的尺寸。
因此,另一种方法是使用自定义
Layout
。
这是一个自定义的
Layout
,可以缩小以适应,但不会放大。其工作原理如下:
sizeForProposal
确定适合建议尺寸的缩放尺寸。sizeThatFits
只是委托给 sizeForProposal
。placeSubviews
应用由sizeForProposal
提供的尺寸。struct ScaledDownToFit: Layout {
typealias Cache = CGSize
func makeCache(subviews: Subviews) -> CGSize {
if let firstSubview = subviews.first {
firstSubview.sizeThatFits(.unspecified)
} else {
.zero
}
}
private func sizeForProposal(proposal: ProposedViewSize, viewSize: CGSize) -> CGSize {
let aspectRatio = viewSize.width / viewSize.height
let constrainedWidth = min(viewSize.width, proposal.width ?? .infinity)
let constrainedHeight = min(viewSize.height, proposal.height ?? .infinity)
let w: CGFloat
let h: CGFloat
if constrainedWidth / constrainedHeight < aspectRatio {
w = constrainedWidth
h = constrainedWidth / aspectRatio
} else {
w = constrainedHeight * aspectRatio
h = constrainedHeight
}
return CGSize(width: w, height: h)
}
public func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout CGSize) -> CGSize {
cache == .zero ? .zero : sizeForProposal(proposal: proposal, viewSize: cache)
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout CGSize) {
if let firstSubview = subviews.first, cache != .zero {
let targetSize = sizeForProposal(proposal: proposal, viewSize: cache)
firstSubview.place(at: bounds.origin, proposal: .init(targetSize))
}
}
}
您可能会发现定义
View
扩展也很有用:
extension View {
func scaledDownToFit() -> some View {
ScaledDownToFit {
self
}
}
}
使用示例:
AsyncImage(
url: image.thumb,
content: { image in
image
.resizable()
.scaledDownToFit()
},
placeholder: {
ProgressView()
}
)
.border(.red)