如何在 SwiftUI 中居中裁剪图像

How to center crop an image in SwiftUI

我是 SwiftUI 的新手。我想每个人都在这一点上。我已经成为一名应用程序开发人员大约 6 年了,我觉得在 Whosebug 上问这个问题很愚蠢。但我到处都看了。如何在 SwiftUI 的 ImageView 中居中裁剪图像?

我知道有一个选项可以更改纵横比,但我只看到适合和填充。我只希望 imageView 居中裁剪(android 术语)图像。有人知道吗?

Android 的 ImageView.ScaleType documentationCENTER_CROP 描述为:

CENTER_CROP

Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions (width and height) of the image will be equal to or larger than the corresponding dimension of the view (minus padding). The image is then centered in the view.

这基本上就是 Aspect Fill Scaling(又名 .scaledToFill())所做的,除了(令人惊讶的是)Aspect Fill 没有不要剪掉框架外的部分。

通过制作图像 .resizable,并应用 .scaledToFill()。图像将按比例缩放以填充其可用框架,并根据需要保留顶部和底部或侧面。 .clipped() 然后删除框架外的图像部分。

Image("myImage")
    .resizable()
    .scaledToFill()
    .frame(width: 200, height: 200, alignment: .center)
    .clipped()

为了更方便,我创建了 extension of Image:

extension Image {
    func centerCropped() -> some View {
        GeometryReader { geo in
            self
            .resizable()
            .scaledToFill()
            .frame(width: geo.size.width, height: geo.size.height)
            .clipped()
        }
    }
}

要使用 Image extension,只需将其放入项目的文件中即可(像 image-centercropped.swift 这样的名称会很好用)。然后只需将 .centerCropped() 添加到您想要居中裁剪的任何图像即可。

Image("apolloimage").centerCropped()

它使用 GeometryReader 来确定它的框架,以便它可以正确地裁剪图像,这意味着 不必指定框架来获得正确的剪裁。您可以随意调整图像的大小,但是您喜欢使用显式框架,或者只需添加 padding()Spacer() 以使其相对于其他用户界面项目保持良好的位置。

例如:如果您希望图像填满 phone 的屏幕:

struct ContentView: View { 
    var body: some View {
        Image("apolloimage")
            .centerCropped()
            .edgesIgnoringSafeArea(.all)
    }
}

通过缩放图像以显示图像的全高或全宽并裁剪其他维度上悬垂的部分,可以很好地显示图像的中心。


示范:

这里有一个演示,展示了图像如何随着图像的增长而居中和裁剪。在此演示中,框架宽度是常量 360,而随着滑块向右移动,框架高度从 50700 变化。在帧较短的开始,图像的顶部和底部被裁剪。由于帧超过原始图像的纵横比,生成的图像居中但在左侧和右侧被裁剪。

struct ContentView: View {
    
    @State private var frameheight: CGFloat = 50
    
    var body: some View {
        VStack(spacing: 20) {
            Spacer()
            Image("apolloimage")
                .resizable()
                .scaledToFill()
                .frame(width: 360, height: self.frameheight)
                .clipped()
            Spacer()
            Slider(value: self.$frameheight, in: 50...700)
                .padding(.horizontal, 20)
        }
    }
}

或使用 .centerCropped() 的等效测试:

struct ContentView: View {
    
    @State private var frameheight: CGFloat = 50
    
    var body: some View {
        VStack(spacing: 20) {
            Spacer()
            Image("apolloimage")
                .centerCropped()
                .frame(width: 360, height: self.frameheight)
            Spacer()
            Slider(value: self.$frameheight, in: 50...700)
                .padding(.horizontal, 20)
        }
    }
}


备用解决方案

制作中心裁剪图像的另一种方法是使图像成为 .overlay()Color.clear。这允许 Color.clear 建立裁剪范围。

Color.clear
.overlay(
    Image("apolloimage")
    .resizable()
    .scaledToFill()
)
.clipped()

和对应的 extensionImage 看起来像这样:

extension Image {
    func centerCropped() -> some View {
        Color.clear
        .overlay(
            self
            .resizable()
            .scaledToFill()
        )
        .clipped()
    }
}

我能够像 iPhone 照片应用那样裁剪图像的正方形中心以便查看。

extension Image {
    func centerSquareCropped() -> some View {
        GeometryReader { geo in
            let length = geo.size.width > geo.size.height ? geo.size.height : geo.size.width
            self
                .resizable()
                .scaledToFill()
                .frame(width: length, height: length, alignment: .center)
                .clipped()
        }
    }
 }

我最初使用的是@vacawama 的答案,它使用 GeometryReader 但发现实际上这不是必需的。

(我用 Xcode 13 和 运行 在 iOS15 中写这篇文章是否有任何区别?)

用这个就够了...

Image(uiImage: image) // insert your own image here :D
  .resizable()
  .aspectRatio(contentMode: .fill)
  .clipped()

我将其用作 ListButtonlabel 参数,所以整个事情就像...

Section("Photo") {
  Button {
    // the action
  } label: {
    if let image = viewStore.imagePickerState.image {
      Image(uiImage: image)
        .resizable()
        .aspectRatio(contentMode: .fill)
        .clipped()
    } else {
      PersonAvatarButton()
    }
  }
}
.aspectRatio(1, contentMode: .fill)
.listRowBackground(Color.gray)
.listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))

我没有为此在任何地方定义框架,只是 Section 的纵横比。

我最终得到的是一个带圆角的方形按钮,照片一直到边缘。调整大小以填充但不以任何方式压扁。按钮大小由屏幕大小决定。

所以我的代码中没有任何具体尺寸。