Home > Back-end >  How to pass and access property from one view to another in SwiftUI?
How to pass and access property from one view to another in SwiftUI?

Time:04-24

I have one view which I am using in another view, and i want to access property from one view to another...but i am not sure how to do it.. (My main problem with below code - I really appreciate if I got solution for this - is I am having dropdown even when it is collapsed its expanding when i clicked outside of it..which is wrong)

import SwiftUI

public struct TopSheet<Content>: View where Content : View {
    private var content: () -> Content
    
    let minHeight: CGFloat = 50.0
    let startHeight: CGFloat = 50.0
    let maxOpacity: CGFloat = 0.8
    let maxArrowOffset: CGFloat = 8.0
    let minimumContentHeight = 61.0
    @State private var currentHeight: CGFloat = 0
    @State private var contentHeight: CGFloat = 0
    @State private var backgroundColor: Color = Color.clear
    @State private var expand = false
    @State private var arrowOffset: Double = 0
    
    public init(@ViewBuilder content: @escaping () -> Content) { self.content = content }
    
    public func expandRatio() -> Double { return max((currentHeight - minHeight) / contentHeight, 0) }
    
    private func isTopSheetExpandable() -> Bool {
        return contentHeight > minimumContentHeight
    }
    
    public var body: some View {
        let tap = TapGesture()
            .onEnded { _ in
                expand.toggle()
                
                if expand {
                    withAnimation(Animation.easeOut) {
                        currentHeight = max(contentHeight, minHeight)
                        self.backgroundColor = Color.grey
                    }
                }
                else {
                    withAnimation(Animation.easeOut) {
                        currentHeight = minHeight
                        self.backgroundColor = Color.clear
                    }
                }
                
                self.arrowOffset = expandRatio() * maxArrowOffset
            }
        
        let drag = DragGesture()
            .onChanged { value in
                currentHeight  = value.translation.height
                currentHeight = max(currentHeight, minHeight)
                
                let opacity = min(expandRatio() * maxOpacity, maxOpacity)
                self.backgroundColor = Color.gray.opacity(opacity)
                
                self.arrowOffset = expandRatio() * maxArrowOffset
            }
            .onEnded { value in
                expand.toggle()
                
                if expand {
                    withAnimation(Animation.easeOut) {
                        currentHeight = max(contentHeight, minHeight)
                        self.backgroundColor = Color.gray.opacity(maxOpacity)
                    }
                }
                else {
                    withAnimation(Animation.easeOut) {
                        currentHeight = minHeight
                        self.backgroundColor = Color.clear
                    }
                }
                
                self.arrowOffset = expandRatio() * maxArrowOffset
            }
        
        VStack(spacing: 0) {
            VStack(alignment: .leading, spacing: 0) {
                VStack(spacing: 0) {
                    GeometryReader { geo in
                        content()
                            .viewHeight()
                            .fixedSize(horizontal: false, vertical: true)
                    }.onPreferenceChange(ViewHeightPreferenceKey.self) { height in
                        contentHeight = height
                        currentHeight = startHeight
                    }.clipped()
                    
                    Spacer(minLength: 0)
                }
                .frame(height: currentHeight)
                
                HStack(alignment: .center) {
                    Spacer()
                    
                    if isTopSheetExpandable() {
                        Arrow(offset: arrowOffset)
                            .stroke(Color.gray, style: StrokeStyle(lineWidth: 4, lineCap: .round, lineJoin: .round))
                            .frame(width: 30, height: 4)
                            .padding(.bottom, 10)
                            .padding(.top, 10)
                    }
                    
                    Spacer()
                }
               // .contentShape(Rectangle())
                    .gesture(drag)
                    .gesture(tap)
                    .animation(.easeInOut, value: 2)
            }
            .background(Color.white)
            
            Spacer()
        }
        .background(self.backgroundColor.edgesIgnoringSafeArea([.vertical, .horizontal, .leading, .trailing, .top, .bottom]))
        .simultaneousGesture(isTopSheetExpandable() ? tap : nil)
    }
}

fileprivate struct Arrow: Shape {
    var offset: Double
    
    func path(in rect: CGRect) -> Path {
        var path = Path()
        
        path.move(to: .zero)
        path.addLine(to: CGPoint(x: rect.width/2, y: -offset))
        path.move(to: CGPoint(x: rect.width/2, y: -offset))
        path.addLine(to: CGPoint(x: rect.width, y: 0))
        
        return path
    }
}

Another view

import SwiftUI

struct NextView: View {
    
    @State private var backgroundColor: Color = Color.gray
    @State private var users: [String] = ["abc", "xyz", "pqr", "mno", "pqr", "ert", ""]
    var accessibilityID: String
    
    var body: some View {
        ZStack {
            Rectangle()
                .fill()
                .foregroundColor(backgroundColor)
            
            TopSheet {
                VStack(spacing: 0) {
                    ForEach($users, id: \.self) { user in
                        HStack {
                            Text(user.wrappedValue)
                                .padding(.vertical, 10)
                            
                            Spacer()
                        }
                        .contentShape(Rectangle())
                        .simultaneousGesture(TapGesture().onEnded {
                            self.users = [user.wrappedValue]   self.users.filter { $0 != user.wrappedValue }
                            switch self.users.first.unsafelyUnwrapped {
                            case "Joe Black": self.backgroundColor = .black
                            case "Eva Green": self.backgroundColor = .green
                            case "Jared Leto": self.backgroundColor = .red
                            default: self.backgroundColor = .gray
                            }
                        })
                        
                    }
                }
                .padding(.horizontal, 10)
            }
        }
    }
}

I might try some trick if I can able to access "expand" property from "TopSheet" and access it in "NextView"

CodePudding user response:

You should store expand as @State in your parent View and pass it via Binding. Simplified example:

public struct TopSheet<Content>: View where Content : View {
    @Binding var expand : Bool
    var content: () -> Content
    
    //declare other properties private so they don't get used in the generated init

    public var body: some View {
        Text("Here")
    }
}
    
struct NextView: View {
    @State private var expand = false
    
    var body: some View {
        TopSheet(expand: $expand) {
            Text("Content")
        }
    }
}

If you really want/need your explicit init, you can do this:

public struct TopSheet<Content>: View where Content : View {
    @Binding private var expand : Bool
    private var content: () -> Content
    
    public init(expand: Binding<Bool>, @ViewBuilder content: @escaping () -> Content) {
        self._expand = expand
        self.content = content
    }
    
    public var body: some View {
        Text("Here")
    }
}
  • Related