While working on my current project, I encountered a design which made use of an horizontal ++code>ScrollView++/code> containing cards. These cards had an elevation, represented using a small shadow. Nothing unusual, or so it would seem.
When I tried implementing this feature, I started with the card and obtained this result. Nothing wrong. We can even see the shadow as a kind of glow around the card.
And then, I tried to put my shiny card into a ++code>ScrollView++/code>, in the main screen of my app ; and there, we have a problem.
You can see the shadow of the card is cut. It is, in fact, clipped down by the ++code>ScrollView++/code> it is nested in, as you can see on the following picture, in which the ++code>ScrollView++/code> is highlighted.
My answer is not a definitive one, but its my best shot at explaining this behaviour.
++code>UIView++/code> embed a ++code>clipToBounds++/code> property, which, if ++code>true++/code> clips nested views to the bounds of the parent view (as the names cleverly implies). For most subclasses of ++code>UIView++/code>, this property is set to ++code>false++/code> (which is the default value).
But for some, this property is set to true. This is the case for ++code>UIScrollView++/code>, upon which ++code>ScrollView++/code> is built (as well as ++code>UIScrollView++/code> subclasses, such as ++code>UITextView++/code>).
However, the size of ++code>ScrollView++/code> should adapt to its children, right? Why isn't the shadow of my cards taken in account? The answer is blurry, as often when dealing with specifics in the Apple ecosystem / documentation, but here is a layman's explanation of what happens:
The padding is already added when the shadows are rendered, but the clipping is yet to be done.
This was the technical answer ; not the rationale behind it. As best as I can tell, this is done because, most of the time, this behaviour is what you would expect from a vertical ++code>UIScrollView++/code>, with components being "clipped" as soon as they're scrolled out of the view.
...But add shadows to the parent view instead.
Because of how view are rendered in SwiftUI, I could apply the ++code>.shadow()++/code> modifier to the ++code>ScrollView++/code> itself. However, this did not make the cut for me, as I needed one card without elevation and this solution would have given every card an elevation, without possibly opting out).
If you know the size of your shadows, nothing prevents you from adding an extra padding around the card to push back the bounds of the ++code>ScrollView++/code> and give some space to the shadows.
However, I personally consider this a bad design, as this space is generally the responsibility of the page rather than that of individual components.
Somewhere in your code, you can add this piece of code:
This will override the ++code>clipToBounds++/code> property, application-wide. This did the trick for me.
If you want to go back to the original behaviour, you can still use the ++code>.clip()++/code> modifier or create a custom ++code>ScrollView++/code> wrapper which would use the ++code>.clip()++/code> modifier.
Another valid solution could be to create a subclass of ++code>UIScrollView++/code> overriding the ++code>.clipToBounds++/code> property, however this would require to re-implement a whole scroll view component, which is not trivial.
All in all, this small issue was a fun one to work with ; moreover, it highlights some very interesting mechanisms of SwiftUI rendering.
Overall, iOS shadow rendering has many other interesting quirks, but these are stories for later times!
Do you know of any other way solve this particular shadow clipping issue?