Home > Software design >  How can I access the parent/Environment of a custom view?
How can I access the parent/Environment of a custom view?

Time:12-14

I want to access the parent of my custom view to know whether my view parent is a HStack or VStack, like Divider() could do it. Currently I am hard coding value, but my goal is that I could be get access the parent information inside my custom view to select the right return view.

struct ContentView: View {
    var body: some View {
        
        HStack { Divider() }
        
        VStack { Divider() }
        
        HStack { CustomView(parentIsHStack: true) }
        
        VStack { CustomView(parentIsHStack: false) }
    }
}


struct CustomView: View {

    let parentIsHStack: Bool
    
    var body: some View {

        if parentIsHStack {
            Text("Parent is HStack")
        }
        else {
            Text("Parent is VStack")
        }
    }
}

CodePudding user response:

I could solve the issue with replacing Apple Stack's, without broking apple api.

struct ContentView: View {
    var body: some View {
        
        HStack { Divider() }
        
        VStack { Divider() }
        
        HStack { CustomView() }
        
        VStack { CustomView() }
        
        ZStack { CustomView() }
        
    }
}


struct CustomView: View {
    
    @Environment(\.stack) var stack
    
    var body: some View {
        
        Text("Parent is "   stack.rawValue)
    }
}

struct HStack<Content>: View where Content: View {
    
    let alignment: VerticalAlignment
    let spacing: CGFloat?
    let content: () -> Content
    
    init(alignment: VerticalAlignment = VerticalAlignment.center, spacing: CGFloat? = nil, @ViewBuilder content: @escaping () -> Content) {
        self.alignment = alignment
        self.spacing = spacing
        self.content = content
    }
    
    var body: some View {
        return SwiftUI.HStack(alignment: alignment, spacing: spacing, content: { content() })
            .environment(\.stack, Stack.hStack)
    }
}

struct VStack<Content>: View where Content: View {
    
    let alignment: HorizontalAlignment
    let spacing: CGFloat?
    let content: () -> Content
    
    init(alignment: HorizontalAlignment = HorizontalAlignment.center, spacing: CGFloat? = nil, @ViewBuilder content: @escaping () -> Content) {
        self.alignment = alignment
        self.spacing = spacing
        self.content = content
    }
    
    var body: some View {
        return SwiftUI.VStack(alignment: alignment, spacing: spacing, content: { content() })
            .environment(\.stack, Stack.vStack)
    }
}

struct ZStack<Content>: View where Content: View {
    
    let alignment: Alignment
    let content: () -> Content
    
    init(alignment: Alignment = Alignment.center, @ViewBuilder content: @escaping () -> Content) {
        self.alignment = alignment
        self.content = content
    }
    
    var body: some View {
        return SwiftUI.ZStack(alignment: alignment, content: { content() })
            .environment(\.stack, Stack.zStack)
    }
}

private struct StackKey: EnvironmentKey { static let defaultValue: Stack = Stack.unknown }

extension EnvironmentValues {
    
    var stack: Stack {
        get { return self[StackKey.self] }
        set(newValue) { self[StackKey.self] = newValue }
    }
    
}

enum Stack: String { case vStack, hStack, zStack, unknown }

CodePudding user response:

I would be tempted to say this is not a hidden environment variable. I don't see a relevant one when I dump all the environment variables (there are a lot though).

Instead, I believe it's how _VariadicView.Tree works. This contains a root and its content. I'll take how HStack works for example. Inspecting the SwiftUI interface, you can see the following snippet of code:


@frozen public struct HStack<Content> : SwiftUI.View where Content : SwiftUI.View {
    @usableFromInline
    internal var _tree: SwiftUI._VariadicView.Tree<SwiftUI._HStackLayout, Content>
    @inlinable public init(alignment: SwiftUI.VerticalAlignment = .center, spacing: CoreGraphics.CGFloat? = nil, @SwiftUI.ViewBuilder content: () -> Content) {
        _tree = .init(
            root: _HStackLayout(alignment: alignment, spacing: spacing), content: content())
    }
    public static func _makeView(view: SwiftUI._GraphValue<SwiftUI.HStack<Content>>, inputs: SwiftUI._ViewInputs) -> SwiftUI._ViewOutputs
    public typealias Body = Swift.Never
}

Notice that the Body is of type Never (therefore a primitive view type). The _tree stores information about the layout, and the type HStackLayout obviously shows this is a HStack.

SwiftUI will be using _makeView(view:inputs:) internally to create the view, which I'm assuming gives special treatment to certain views.

You'll need to make custom versions of HStack/VStack and pass an environment variable down to know which kind your subview is in.

  • Related