Reputation: 1994
I'm working though some examples to get familiar with SwiftUI and I am having an issue with sizing.
I'm used to the way UIKit handles resizing views, where with AutoLayout you can essentially make a parent size to it's children. This does not seem to be the case with SwiftUI.
Everything that I've read has shown how to use GeometryReader on the child to get the size of its parent is suggesting, and that the child can then choose it's own size, but I have not seen how to get the child's size from the parent.
Say I have the following code:
struct ContentView: View {
var body: some View {
Text("This is Text").ripple(rippleColor: .lightGray, rippleOpacity: 0.5)
}
}
ripple()
is an extension function that adds a custom ViewModifier
that overlays the content onto a UIViewRepresentable
view for the purposes of creating a material ripple effect when tapping.
struct RippleModifier: ViewModifier {
let rippleColor: UIColor
let rippleOpacity: Float
func body(content: Content) -> some View {
RippleView(
rippleColor: rippleColor,
rippleOpacity: rippleOpacity
)
.overlay(content)
}
}
extension View {
func ripple(rippleColor: UIColor, rippleOpacity: Float) -> some View {
modifier(
RippleModifier(
rippleColor: rippleColor,
rippleOpacity: rippleOpacity
)
)
}
}
The issue I'm having is that when I just have the Text
, it sets its frame to fit its content (the string). When I add the .ripple()
modifier to it, the frame then becomes the entire screen.
With UIKit, I'd just add some constraints to pin what would be the UILabel to the parent, and I'd let that dictate the size of the parent.
Is there a way to do with with SwiftUI?
Bonus question:
Related but unrelated, how do I get the taps on the Text
to fall through to the view it's overlaid on? The ripple will activate and properly animate when tapping anywhere within the frame of the view with the exception of tapping directly on the text itself.
Here is partial code for the UIViewRepresentable view.
struct RippleView: UIViewRepresentable {
var rippleColor: UIColor = .systemGray
var rippleOpacity: Float = 0.5
func makeUIView(context: Context) -> UIRippleView {
UIRippleView()
}
func updateUIView(
_ uiView: UIRippleView,
context: UIViewRepresentableContext<RippleView>
) {
uiView.rippleColor = rippleColor
uiView.rippleOpacity = rippleOpacity
}
}
open class UIRippleView: UIView {
open var rippleColor: UIColor = .systemGray {
didSet {
rippleLayer.fillColor = rippleColor.cgColor
}
}
open var rippleOpacity: Float = 0.5 {
didSet {
rippleLayer.opacity = rippleOpacity
}
}
private lazy var rippleLayer: CAShapeLayer = {
let layer = CAShapeLayer()
layer.opacity = 0.0
self.layer.insertSublayer(layer, at: 0)
return layer
}()
public override init(frame: CGRect) {
super.init(frame: frame)
clipsToBounds = true
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
clipsToBounds = true
}
}
There's an extension to that view that overrides the touch functions to calculate where the tap happened and to perform the animations, but that just modifies the path
of the rippleLayer
.
Upvotes: 0
Views: 2353
Reputation: 13283
You can change overlay
to background
like this:
struct RippleModifier: ViewModifier {
let rippleColor: UIColor
let rippleOpacity: Float
func body(content: Content) -> some View {
content.background(
RippleView(
rippleColor: rippleColor,
rippleOpacity: rippleOpacity
))
}
}
So the RippleView
gets the size constraint from the content
and not the other way around.
Upvotes: 1