【问题标题】:Clip image to square in SwiftUI在 SwiftUI 中将图像裁剪为正方形
【发布时间】:2020-02-06 00:18:39
【问题描述】:

我正在尝试将多个单元格彼此相邻放置,其中每个单元格由下面的图像和文本组成。单元格本身应该是一个正方形,并且应该缩放图像以填充剩余空间(剪切图像的一部分)。

首先,我尝试将图像制作为正方形,并在下方显示文字。 现在我的问题是,我不知道如何在 SwiftUI 中正确实现这一点。使用此代码时,我可以让它工作:

VStack {
    Image(uiImage: recipe.image!)
        .resizable()
        .aspectRatio(contentMode: .fill)
        .frame(width: 200, height: 200, alignment: .center)
        .clipped()
    Text(recipe.name)
}

问题是,我必须指定一个固定的帧大小。我想要的是一种制作单元格的方法,它可以保持 1:1 的纵横比并且可以调整大小,这样我就可以将它们的动态数量放在彼此相邻的屏幕上。

我也试过

VStack {
    Image(uiImage: recipe.image!)
        .resizable()
        .aspectRatio(1.0, contentMode: .fit)
        .clipped()
    Text(recipe.name)
}

这给了我方形图像,动态缩放。但问题是,图像现在被拉伸以填充正方形,而不是缩放以填充它。

我的下一个想法是把它剪成一个正方形。为此,我首先尝试将其剪辑成圆形(因为显然没有方形):

VStack {
    Image(uiImage: recipe.image!)
        .resizable()
        .aspectRatio(contentMode: .fit)
        .clipShape(Circle())
    Text(recipe.name)
}

但出于某种奇怪的原因,它并没有真正剪切图像,而是保留了剩余空间...

所以我是没有看到什么东西,还是唯一的选择是将图像与框架修饰符对齐?

编辑

澄清一下:我不太关心文本,只关心整个单元格(或者更简单的图像)是方形的,无需通过.frame 指定其大小,也无需非方形原件图像被拉伸以使其适合。

因此,完美的解决方案是 VStack 是方形的,但获得方形图像也可以。它应该看起来像图 1,但不必使用 .frame 修饰符。

【问题讨论】:

    标签: ios swiftui


    【解决方案1】:

    ZStack 将帮助我们解决这个问题,它允许我们在不影响另一个布局的情况下分层视图。

    对于正文:

    .frame(minWidth: 0, maxWidth: .infinity) 将文本水平扩展至其父级的大小

    .frame(minHeight: 0, maxHeight: .infinity) 在其他情况下很有用

    至于图片:

    .aspectRatio(contentMode: .fill) 使图像保持其纵横比,而不是压缩到其框架的大小。

    .layoutPriority(-1) 取消布局图像的优先级以防止其扩展其父级(在我们的例子中,ForEach 中的ZStack)。

    layoutPriority 的值只需低于默认设置为 0 的父视图。我们必须这样做,因为 SwiftUI 将在其父级之前布局一个子级,并且父级必须处理子级大小,除非我们手动设置不同的优先级。

    .clipped() 修饰符使用边框来遮盖视图,因此您需要将其设置为剪裁任何不是 1:1 宽高比的图像。

        var body: some View {
            HStack {
                ForEach(0..<3, id: \.self) { index in
                    ZStack {
                        Image(systemName: "doc.plaintext")
                            .resizable()
                            .aspectRatio(contentMode: .fill)
                            .layoutPriority(-1)
                        VStack {
                            Spacer()
                            Text("yes")
                                .frame(minWidth: 0, maxWidth: .infinity)
                                .background(Color.white)
                        }
                    }
                    .clipped()
                    .aspectRatio(1, contentMode: .fit)
                    .border(Color.red)
                }
            }
        }
    

    编辑:虽然几何阅读器非常有用,但我认为应尽可能避免使用它们。让 SwiftUI 完成工作会更干净。这是我使用Geometry Reader 的初始解决方案,也同样有效。

            HStack {
                ForEach(0..<3, id: \.self) { index in
                    ZStack {
                        GeometryReader { proxy in
                            Image(systemName: "pencil")
                                .resizable()
                                .scaledToFill()
                                .frame(width: proxy.size.width)
                            VStack {
                                Spacer()
                                Text("yes")
                                    .frame(width: proxy.size.width)
                                    .background(Color.white)
                            }
                        }
                    }
                    .clipped()
                    .aspectRatio(1, contentMode: .fit)
                    .border(Color.red)
                }
            }
    
    

    【讨论】:

    • 文本不是问题。我的主要问题是使图像呈正方形,而无需手动指定其大小(通过.frame)。如果我找到使用 SwiftUI 切割图像正方形的解决方案,然后在其下方/上方放置一个文本,我就可以了。此外,您的解决方案仍然会拉伸图像。如果您不使用“铅笔”,而是使用不是方形的图像,它将拉伸它以使其成为方形,而不是剪切它。
    • @iComputerfreak 更新了我的答案以更好地解决您的问题。
    • 我把clipped()放在第一位,但这似乎并不重要。此外,如果为图像的框架添加height: proxy.size.height 参数,图像将垂直居中。
    • 可以使用这两种技术重现效果。这是一个可运行的版本(带有 cookie 图像!):github.com/ralfebert/SwiftUIPlayground/blob/master/…
    • layoutPriority(-1) 成功了。非常感谢:你为我节省了很多时间和精力。
    【解决方案2】:

    它对我有用,但我不知道为什么cornerRadius 是必要的......

    import SwiftUI
    
    struct ClippedImage: View {
        let imageName: String
        let width: CGFloat
        let height: CGFloat
    
        init(_ imageName: String, width: CGFloat, height: CGFloat) {
            self.imageName = imageName
            self.width = width
            self.height = height
        }
        var body: some View {
            ZStack {
                Image(imageName)
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .frame(width: width, height: height)
            }
            .cornerRadius(0) // Necessary for working
            .frame(width: width, height: height)
        }
    }
    
    struct ClippedImage_Previews: PreviewProvider {
        static var previews: some View {
            ClippedImage("dishLarge1", width: 100, height: 100)
        }
    }
    

    【讨论】:

    • 谢谢 - ZStack 中图像上的 .layoutPriority(-1) 对我有用
    【解决方案3】:

    这是对我有用的答案:

    VStack {
      Color.clear
        .aspectRatio(1, contentMode: .fit)
        .background(Image(uiImage: recipe.image!)
                      .resizable()
                      .aspectRatio(contentMode: .fill))
        .clipped()
      Text(recipe.name)
    }
    

    我使用清晰的颜色,然后使用 fit 设置纵横比。这使容器呈方形。然后我添加了图像的背景设置为填充。最重要的是,我添加了裁剪,这样背景就不会溢出正方形的边缘。

    【讨论】:

      【解决方案4】:

      这是我在Reddit 上找到的另一个解决方案,并做了一些改进:

      Color.clear
          .aspectRatio(1, contentMode: .fit)
          .overlay(
              Image(imageName)
                  .resizable()
                  .scaledToFill()
              )
          .clipShape(Rectangle())
      

      它类似于 Chads 的回答,但不同的是你放置图像相对于清晰颜色(背景与叠加)的方式

      奖励:要让它具有圆形形状,只需使用.clipShape(Circle()) 作为最后一个修饰符。其他一切都保持不变

      【讨论】:

        【解决方案5】:

        答案基于@ramzesenok 的答案,但包含在视图修饰符中

        修饰符:

        struct FitToAspectRatio: ViewModifier {
        
            let aspectRatio: Double
            let contentMode: SwiftUI.ContentMode
        
            func body(content: Content) -> some View {
                Color.clear
                    .aspectRatio(aspectRatio, contentMode: .fit)
                    .overlay(
                        content.aspectRatio(nil, contentMode: contentMode)
                    )
                    .clipShape(Rectangle())
            }
        
        }
        

        您还可以选择添加扩展功能以方便访问

        extension Image {
            func fitToAspect(_ aspectRatio: Double, contentMode: SwiftUI.ContentMode) -> some View {
                self.resizable()
                    .scaledToFill()
                    .modifier(FitToAspectRatio(aspectRatio: aspectRatio, contentMode: contentMode))
            }
        }
        

        然后简单

        Image(...).fitToAspect(1, contentMode: .fill)
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-04-23
          • 2013-03-08
          • 2023-03-21
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多