0

I'm trying to inset a path by a constant amount, but for some reason, it appears this is not working. What am I doing wrong?

This works fine; it draws a rectangle of 250x250 with a red border.

let frame = CGRect(origin: .zero, size: CGSize(width: 250, height: 250))

VStack {
    Path { path in
        path.addRect(frame)
    }
    .border(Color.red)
}.frame(width: frame.width, height: frame.height)

Now, I would like to inset this shape by an arbitrary amount, let's say 20. I would assume I can use the path(in:) method to do this, but this appears to be not working.

let frame = CGRect(origin: .zero, size: CGSize(width: 250, height: 250))

VStack {
    Path { path in
        path.addRect(frame)
    }
    .path(in: frame.insetBy(dx: 20, dy: 20))
    .border(Color.red)
}.frame(width: frame.width, height: frame.height)

I would assume to see a smaller rectangle of 230x230 with a red border, but instead I see a 250x250 rectangle with a red border. Why is this? How can I make it so the inset is applied correctly?

PS: This addRect(frame) function can of course be written as addRect(frame.insetBy(dx: 20, dy: 20)), but the rect is here as per demonstration. In practice I have a path with several lines and points that do not work with the insetBy(dx:dy:) function

Bram
  • 2,718
  • 1
  • 22
  • 43
  • *"... In practice I have a path with several lines and points ..."* -- I strongly recommend that you **start** with a simple "non-rectangle" path, so you don't go down a road of "here's how I modify a rect, why doesn't it work with something else." Try heading over to Google (or your favorite search engine) and search for `SwiftUI Path scale` -- you should find lots of discussion about using `CGAffineTransform` to scale / move / rotate paths. – DonMag Nov 21 '22 at 15:14
  • @DonMag Clearly stated above, an example with just a simple shape that does not work, looking forward to a more constructive answer or comment – Bram Nov 21 '22 at 21:13
  • *sigh* ... I did some searching for you, and came up with a quick example extension that you may find helpful... see my answer. – DonMag Nov 21 '22 at 22:38

1 Answers1

0

Quick example extension for using inset on a Path:

extension Path {
    func insetBy(dx: CGFloat, dy: CGFloat) -> Path {
        let wMultiplier = 1.0 - (dx / self.boundingRect.width)
        let hMultiplier = 1.0 - (dy / self.boundingRect.height)
        let tr = CGAffineTransform(scaleX: wMultiplier, y: hMultiplier).translatedBy(x: dx * 0.5, y: dy * 0.5)
        return self.applying(tr)
    }
}

struct TestView: View {
    
    let frame = CGRect(origin: .zero, size: CGSize(width: 250, height: 250))
    
    var body: some View {
        
        VStack {
            Path { path in
                path.addRect(frame)
            }
            .insetBy(dx: 20, dy: 20)
            .stroke(lineWidth: 2)
            .foregroundColor(Color.red)
            .background(Color.yellow)
        }
        .frame(width: frame.width, height: frame.height)
        
    }
    
}

I would, however, strongly suggest searching and reading up on other approaches. This looks very flexible: https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-uibezierpath-and-cgpath-in-swiftui

DonMag
  • 69,424
  • 5
  • 50
  • 86
  • Sure, this is indeed what I currently have built as well (made it without Google), but this doesn't explain why the `path(in:)` doesn't work as I'd expect and I can't figure out how it would work – Bram Nov 21 '22 at 23:16
  • @Bram - the documentation is rather brief, but it does not appear that `.path(in:...)` is meant to be used that way. Searching indicates that is a method - `func path(in rect: CGRect) -> Path { ... }` - to implement inside a Shape struct. See the link I included in my answer. – DonMag Nov 23 '22 at 15:03