如何缩放 SwiftUI 图像,包括它的覆盖层?

How to scale a SwiftUI Image including its overlay?

鉴于此视图的图像具有叠加层:

struct TestView: View {
    var body: some View {
        Image("image")
            .overlay {
                Image("logo")
                    .position(x: 20, y: 130) // I want to freely position the overlay image
            }
    }
}

图像的大小无关紧要。我的示例是 150×150 像素(“图像”)和 20×20 像素(徽标)。

我怎样才能将它放大到它的边界(比如 superviews 框架),包括 叠加层?

一些必备条件:

  • 我需要自由定位叠加图片,所以我不能将overlayalignment参数与徽标上的填充。

  • 我还需要定位叠加图像绝对,而不是相对。所以我不能使用 GeometryReader(或 alignment)。

  • 我需要视图在不同场景(不同设备、不同视图层次结构)中可重用。这意味着我不知道比例因子,所以我不能使用scaleEffect

我试过了:

A) .aspectRatio(contentMode: .fit)

struct TestView: View {
    var body: some View {
        Image("image")
            .resizable()
            .overlay {
                Image("logo")
                    .position(x: 20, y: 130)
            }
            .aspectRatio(contentMode: .fit)
    }
}

B) .scaledToFit()

struct TestView: View {
    var body: some View {
        Image("image")
            .resizable()
            .overlay {
                Image("logo")
                    .position(x: 20, y: 130)
            }
            .scaledToFit()
    }
}

C) 使徽标可调整大小

struct TestView: View {
    var body: some View {
        Image("image")
            .resizable()
            .overlay {
                Image("logo")
                    .resizable()
                    .position(x: 20, y: 130)
            }
            .scaledToFit() // or .aspectRatio(contentMode: .fit)
        }
}

A 和 B 看起来像这样:

C 看起来像这样:

两者都给了我一个与原始徽标不同的相对位置(参见链接的屏幕截图)。相对大小也不同(现在太小了)。

包裹在堆栈中并改为缩放此堆栈也没有帮助。

[更新] 接受的答案

显然,在 SwiftUI 和 UIKit 中没有真正的视图层次结构是有代价的。缩放层次结构不应该那么复杂恕我直言。相反,我仍然希望 .scaledToFit(如果在末尾添加)扩展它之前的所有内容(参见 A、B)和 C))。

改编(减去contentSizealignment: .topLeading):

struct ContentView: View {
    var body: some View {
        Image("image")
            .resizable()
            .scaledToFit()
            .overlay {
                GeometryReader { geometry in
                    let imageSize = CGSize(width: 150, height: 150)
                    let logoSize = CGSize(width: 20, height: 20)
                    let logoPosition = CGPoint(x: 20, y: 130)
                    Image("logo")
                        .resizable()
                        .position(
                            x: logoPosition.x / imageSize.width * geometry.size.width,
                            y: logoPosition.y / imageSize.height * geometry.size.height
                        )
                        .frame(
                            width: logoSize.width / imageSize.width * geometry.size.width,
                            height: logoSize.height / imageSize.height * geometry.size.height
                        )
                }
            }
    }
}

问题是徽标的绝对定位。我看到两种方式:

  1. 使用 .scaleEffect 缩放整个结果图像。
    请注意,这是底层图像的渲染缩放,它不会以新尺寸重新绘制,因此可能会变得模糊。

     Image("dog")
         .overlay {
             Image(systemName: "circle.hexagongrid.circle.fill")
                 .foregroundColor(.pink)
                 .position(x: 20, y: 130)
         }        
         .scaleEffect(2.0)
    

或者 – 以及我的偏好
2. Logo相对于左下角的位置

    ZStack(alignment: .bottomLeading) {
        Image("dog")
            .resizable()
            .scaledToFit()
        Image(systemName: "circle.hexagongrid.circle.fill")
            .resizable()
            .foregroundColor(.pink)
            .frame(width: 70, height: 70)
            .padding()
    }

然后像这样使用 GeometryReader:

struct ContentView: View {
    
    let imageSize = CGSize(width: 150, height: 150) // Size of orig. image
    let logoPos = CGSize(width: 10, height: 120) // Position of Logo in relation to orig. image
    let logoSize = CGSize(width: 20, height: 20) // Size of logo in relation to orig. image
    
    @State private var contentSize = CGSize.zero
    
    var body: some View {
        
        Image("dog")
            .resizable()
            .scaledToFit()
        
            .overlay(
                GeometryReader { geo in
                    Color.clear.onAppear {
                        contentSize = geo.size
                    }
                }
            )
            .overlay(
                Image(systemName: "circle.hexagongrid.circle.fill")
                    .resizable()
                    .foregroundColor(.pink)
                    .offset(x: logoPos.width / imageSize.width * contentSize.width,
                            y: logoPos.height / imageSize.height * contentSize.height)
                    .frame(width: logoSize.width / imageSize.width * contentSize.width,
                           height: logoSize.height / imageSize.height * contentSize.height)
                , alignment: .topLeading)
    }
}