I’m trying to recreate Apple’s festival lights image in SwiftUI (screenshot from Apple India’s website). Expected result:
Here’s what I’ve managed to achieve so far:
MY UNDERSTANDING SO FAR: Images are not Shapes, so we can’t stroke their borders, but I also found that shadow() modifier places shadows on image borders just fine. So, I need a way to customize the shadow somehow and understand how it works.
WHAT I’VE TRIED SO FAR: Besides the code above, I tried to unsuccessfully convert a given SF Symbol to a Shape
using Vision framework’s contour detection, based on my understanding of this article: https://www.iosdevie.com/p/new-in-ios-14-vision-contour-detection
Can someone please guide me on how I would go about doing this, preferably using SF symbols only.
2
Answers
Looks like the Vision contour detection isn't a bad approach after all. I was just missing a few things, as helpfully pointed out by @DonMag. Here's my final answer using SwiftUI, in case someone's interested.
First, we create an
InsettableShape
:Then we create our main view:
Final look:
We can use the
Vision
framework withVNDetectContourRequestRevision1
to get acgPath
:The path will be based on a
0,0 1.0,1.0
coordinate space, so to use it we need to scale the path to our desired size. It also uses inverted Y-coordinates, so we’ll need to flip it also:Couple notes…
When using
UIImage(systemName: "applelogo")
, we get an image with "font" characteristics – namely, empty space. See this https://stackoverflow.com/a/71743787/6257435 and this https://stackoverflow.com/a/66293917/6257435 for some discussion.So, we could use it directly, but it makes the path scaling and translation a bit complex.
So, instead of this "default":
we can use a little code to "trim" the space for a more usable image:
Then we can use the path from
Vision
as the path of aCAShapeLayer
, along with these layer properties:.lineCap = .round
/.lineWidth = 8
/.lineDashPattern = [2.0, 20.0]
(for example) to get a "dotted line" stroke:Then we can use that same path on a shape layer as a mask on a gradient layer:
and finally remove the image view so we see only the view with the masked gradient layer:
Here’s example code to produce that: