【问题标题】:SwiftUI Path as contentShape, doesn't line up with ImageSwiftUI Path 作为 contentShape,不与 Image 对齐
【发布时间】:2020-02-19 23:13:41
【问题描述】:

TL;DR:我使用Path 来指定Image 的命中区域。但我不知道如何调整 Path 坐标以匹配 SwiftUI 决定的布局......或者我什至需要这样做。

运行时

我的(测试)应用看起来像这样,Image 边框(不是帧)为清楚起见着色:

想要是让Image 处理不透明橙色的水龙头。在不透明的橙色之外点击——即使在橙色图像的范围内——应该“穿过”到绿色图像或灰色背景。但不是。 (紫色轮廓显示路径的位置,根据自身情况;请参见下面的代码)。

详情

这是图像的内在(以像素为单位)布局:

不透明部分周围的路径很容易被视为

[(200, 200), (600, 200), (600, 600), (200, 600)] 

这些坐标和Image 坐标有什么关系?

代码

extension CGPoint {
    typealias Tuple = (x:CGFloat, y:CGFloat)
    init(tuple t: Tuple) {
        self.init(x: t.x, y: t.y)
    }
}

struct ContentView: View {

    var points = [(200, 200), (600, 200), (600, 600), (200, 600)]
        .map(CGPoint.init(tuple:))
        // .map { p in p.shifted(dx:-64, dy:-10)}  // Didn't seem to help.

    var path : Path {
        var result = Path()
        result.move(to: points.first!)
        result.addLines(points)
        result.closeSubpath()
        return result
    }

    var body: some View {
        ZStack{
            Image("gray")       // Just so we record all touches.
            .resizable()
            .frame(maxWidth : .infinity,maxHeight: .infinity)
            .onTapGesture {
                print("background")
            }

            Image("square_green")
            .resizable()
            .scaledToFit()
            .border(Color.green, width: 4)
            .offset(x: 64, y:10)    // So the two Images don't overlap completely.
            .onTapGesture {
                print("green")
            }

            Image("square_orange")
            .resizable()
            .scaledToFit()
            .contentShape(path)        // Magic should happen here.
            .border(Color.orange, width: 4)
            .offset(x: -64, y:-10)
            // .contentShape(path)     // Didn't work here either.
            .onTapGesture {
                 print("orange")
            }

            path.stroke(Color.purple)  // Origin at absolute (200,200) as expected.

        }
    }
}

【问题讨论】:

    标签: layout swiftui mask hittest


    【解决方案1】:

    Asperi 是正确的,当我阅读 Paul Hudson 并掌握了 Shape 的(单一)要求时,我恍然大悟——路径(in rect: CGRect)-> 路径方法。 rect 参数告诉您有关局部坐标系的所有信息:即其大小。

    我的工作代码现在看起来像这样。

    帮助者

    extension CGPoint {
        func scaled(xFactor:CGFloat, yFactor:CGFloat) -> CGPoint {
            return CGPoint(x: x * xFactor, y: y * yFactor)
        }
    
        typealias SelfMap = (CGPoint) -> CGPoint
        static func scale(_ designSize: CGSize, into displaySize: CGSize) -> SelfMap {{
            $0.scaled(
                xFactor: displaySize.width  / designSize.width,
                yFactor: displaySize.height / designSize.height
            )
        }}
    
        typealias Tuple = (x:CGFloat, y:CGFloat)
        init(tuple t: Tuple) {
            self.init(x: t.x, y: t.y)
        }
    }
    

    在适当的上下文中绘制路径

    // This is just the ad-hoc solution. 
    // You will want to parameterize the designSize and points.
    
    let designSize = CGSize(width:800, height:800)
    let opaqueBorder = [(200, 200), (600, 200), (600, 600), (200, 600)]
    
    // To find boundary of real-life images, see Python code below.
    
    struct Mask : Shape {
        func path(in rect: CGRect) -> Path {
            let points = opaqueBorder
                .map(CGPoint.init(tuple:))
    
                // *** Here we use the context *** (rect.size)
                .map(CGPoint.scale(designSize, into:rect.size))
    
            var result = Path()
            result.move(to: points.first!)
            result.addLines(points)
            result.closeSubpath()
            return result
        }
    }
    

    使用面具

    struct ContentView: View {
        var body: some View {
            ZStack{
                Image("gray")           // Just so we record all touches.
                .resizable()
                .frame(
                    maxWidth : .infinity,
                    maxHeight: .infinity
                )
                .onTapGesture {
                        print("background")
                }
    
                // Adding mask here left as exercise.
                Image("square_green")
                .resizable()
                .scaledToFit()
                .border(Color.green, width: 4)
                .offset(x: 64, y:10)    // So the two Images don't overlap completely.
                .onTapGesture {
                    print("green")
                }
    
                Image("square_orange")
                .resizable()
                .scaledToFit()
                .border(Color.orange, width: 4)
    
               // Sanity check shows the Mask outline.
                .overlay(Mask().stroke(Color.purple))
    
                // *** Actual working Mask ***
                .contentShape(Mask())
    
                .offset(x: -64, y:-10)
                .onTapGesture {
                    print("orange")
                } 
            }
        }
    }
    

    获取大纲

    #!/usr/bin/python3
    
    # From https://www.reddit.com/r/Python/comments/f2kv1/question_on_tracing_an_image_in_python_with_pil
    
    import sys
    import os
    os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide"
    import pygame
    
    # Find simple border.
    
    fname   = sys.argv[1]
    image   = pygame.image.load(fname)
    bitmask = pygame.mask.from_surface(image) 
    comp    = bitmask.connected_component() 
    outline = comp.outline(48)
    
    print("name:  ", fname)
    print("size:  ", image.get_rect().size)
    print("points:", outline)
    
    # Sanity check.
    # From https://www.geeksforgeeks.org/python-pil-imagedraw-draw-polygon-method
    
    from PIL import Image, ImageDraw, ImagePath
    import math
    
    box = ImagePath.Path(outline).getbbox()
    bsize = list(map(int, map(math.ceil, box[2:])))
    
    im = Image.new("RGB", bsize, "white")
    draw = ImageDraw.Draw(im)
    draw.polygon(outline, fill="#e0c0ff", outline="purple")
    
    im.show()      # That this works is amazing.
    

    【讨论】:

    • 感谢分享您的解决方案。这是一个很大的帮助。
    【解决方案2】:

    1) .offset 只移动内容,但视图在视图层次布局中的位置没有改变,所以请改用.position

    2) contentShape 的形状应该在视图坐标空间中构造

    【讨论】:

    • 是的,是的。特别是第 2 点)。我在发布的解决方案中留下了offset。必须为我的继任者留下一些工作。
    猜你喜欢
    • 2020-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-19
    • 1970-01-01
    • 2020-05-23
    • 1970-01-01
    相关资源
    最近更新 更多