programing

스위프트 하나를 통과하는 방법UI 뷰를 다른 뷰 구조의 변수로 사용

muds 2023. 11. 7. 21:11
반응형

스위프트 하나를 통과하는 방법UI 뷰를 다른 뷰 구조의 변수로 사용

사용자 지정 탐색 링크(NavigationLink)를 구현하고 있습니다.MenuItem프로젝트 전반에 걸쳐 재사용하고 싶습니다.그것은 그것에 부합하는 구조입니다.View실행.var body : some View그 안에 들어있는.NavigationLink. 나는 어떻게든 제시할 견해를 저장할 필요가 있습니다.NavigationLink의 몸속에MenuItem하지만 아직 그렇게 하지 못했습니다.

정의했습니다.destinationView인에MenuItem로서의 몸.some View그리고 두 개의 이니셜라이저를 시도했습니다.

너무 쉬워 보였습니다.

struct MenuItem: View {
    private var destinationView: some View

    init(destinationView: View) {
        self.destinationView = destinationView
    }

    var body : some View {
        // Here I'm passing destinationView to NavigationLink...
    }
}

--> 오류: 프로토콜 'View'는 자체 또는 관련 유형 요구 사항이 있으므로 일반 제약 조건으로만 사용할 수 있습니다.

두 번째 시도:

struct MenuItem: View {
    private var destinationView: some View

    init<V>(destinationView: V) where V: View {
        self.destinationView = destinationView
    }

    var body : some View {
        // Here I'm passing destinationView to NavigationLink...
    }
}

--> 오류: '일부 보기' 유형에 'V' 유형 값을 할당할 수 없습니다.

마지막 시도:

struct MenuItem: View {
    private var destinationView: some View

    init<V>(destinationView: V) where V: View {
        self.destinationView = destinationView as View
    }

    var body : some View {
        // Here I'm passing destinationView to NavigationLink...
    }
}

--> 오류: '일부 보기' 유형에 '보기' 유형 값을 할당할 수 없습니다.

누가 도와줬으면 좋겠습니다.NavigationLink가 일부 보기를 인수로 받아들일 수 있는 경우 방법이 있어야 합니다.감사합니다;D

여기서 제가 읽은 모든 것과 저에게 효과가 있었던 해결책을 요약하자면 다음과 같습니다.

struct ContainerView<Content: View>: View {
    @ViewBuilder var content: Content
    
    var body: some View {
        content
    }
}

이것은 단순하게 표현할 수 있을 뿐만 아니라View안에서, 또한 덕분에@ViewBuilder,사용하다if-else그리고.switch-case블록:

struct SimpleView: View {
    var body: some View {
        ContainerView {
            Text("SimpleView Text")
        }
    }
}

struct IfElseView: View {
    var flag = true
    
    var body: some View {
        ContainerView {
            if flag {
                Text("True text")
            } else {
                Text("False text")
            }
        }
    }
}

struct SwitchCaseView: View {
    var condition = 1
    
    var body: some View {
        ContainerView {
            switch condition {
            case 1:
                Text("One")
            case 2:
                Text("Two")
            default:
                Text("Default")
            }
        }
    }
}

보너스: 가능한 모든 공간(부감에 필요한 공간만 요구하는 위의 컨테이너와는 반대로)을 요구하는 욕심쟁이 컨테이너를 원한다면 다음과 같습니다.

struct GreedyContainerView<Content: View>: View {
    @ViewBuilder let content: Content
    
    var body: some View {
        content
            .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

보기에 이니셜라이저가 필요한 경우 다음을 사용할 수 있습니다.@ViewBuilder파라미터도 마찬가지입니다.여러 개의 매개변수에 대해서도 다음을 수행할 수 있습니다.

init(@ViewBuilder content: () -> Content) {…}

애플이 하는 방식은 기능 빌더를 사용하는 것입니다.다음과 같은 사전 정의된 것이 있습니다.ViewBuilder. 그것을 당신의 마지막 논쟁, 혹은 유일한 논쟁으로 삼으세요.init방법MenuItem, 다음과 같습니다.

..., @ViewBuilder builder: @escaping () -> Content)

다음과 같이 정의된 속성에 할당합니다.

let viewBuilder: () -> Content

그런 다음 전달된 보기를 표시하려면 다음과 같이 함수를 호출합니다.

HStack {
    viewBuilder()
}

새 보기를 다음과 같이 사용할 수 있습니다.

MenuItem {
   Image("myImage")
   Text("My Text")
}

이렇게 하면 최대 10개의 보기를 통과하여 사용할 수 있습니다.if조건 등이 더 제한적이기를 원한다면 자신의 함수 작성기를 정의해야 할 것입니다.나는 그렇게 하지 않았으니 당신은 그것을 검색해야 할 것입니다.

일반 매개 변수를 의 일부로 만들어야 합니다.MenuItem:

struct MenuItem<Content: View>: View {
    private var destinationView: Content

    init(destinationView: Content) {
        self.destinationView = destinationView
    }

    var body : some View {
        // ...
    }
}

다음과 같이 사용자 정의 보기를 만들 수 있습니다.

struct ENavigationView<Content: View>: View {

    let viewBuilder: () -> Content

    var body: some View {
        NavigationView {
            VStack {
                viewBuilder()
                    .navigationBarTitle("My App")
            }
        }
    }

}

struct ENavigationView_Previews: PreviewProvider {
    static var previews: some View {
        ENavigationView {
            Text("Preview")
        }
    }
}

사용방법:

struct ContentView: View {

    var body: some View {
        ENavigationView {
            Text("My Text")
        }
    }

}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

다음과 같이 Navigation Link(또는 기타 보기 위젯)를 변수로 하위 보기로 전달할 수 있습니다.

import SwiftUI

struct ParentView: View {
    var body: some View {
        NavigationView{

            VStack(spacing: 8){

                ChildView(destinationView: Text("View1"), title: "1st")
                ChildView(destinationView: Text("View2"), title: "2nd")
                ChildView(destinationView: ThirdView(), title: "3rd")
                Spacer()
            }
            .padding(.all)
            .navigationBarTitle("NavigationLinks")
        }
    }
}

struct ChildView<Content: View>: View {
    var destinationView: Content
    var title: String

    init(destinationView: Content,  title: String) {
        self.destinationView = destinationView
        self.title = title
    }

    var body: some View {
        NavigationLink(destination: destinationView){
            Text("This item opens the \(title) view").foregroundColor(Color.black)
        }
    }
}

struct ThirdView: View {
    var body: some View {
        VStack(spacing: 8){

            ChildView(destinationView: Text("View1"), title: "1st")
            ChildView(destinationView: Text("View2"), title: "2nd")
            ChildView(destinationView: ThirdView(), title: "3rd")
            Spacer()
        }
        .padding(.all)
        .navigationBarTitle("NavigationLinks")
    }
}

인정된 답변은 훌륭하고 간단합니다.iOS 14 + macOS 11로 구문이 더욱 깔끔해졌습니다.

struct ContainerView<Content: View>: View {
  @ViewBuilder var content: Content
    
  var body: some View {
    content
  }
}

그런 다음 계속 이렇게 사용합니다.

ContainerView{
  ...
}

저는 정말로 제 일을 하기 위해 노력했습니다.View. 호출 방법에 대한 자세한 내용은 여기에 나와 있습니다.

확장자:View(일반인 사용) - 기억하세요import SwiftUI:

extension View {

    /// Navigate to a new view.
    /// - Parameters:
    ///   - view: View to navigate to.
    ///   - binding: Only navigates when this condition is `true`.
    func navigate<SomeView: View>(to view: SomeView, when binding: Binding<Bool>) -> some View {
        modifier(NavigateModifier(destination: view, binding: binding))
    }
}


// MARK: - NavigateModifier
fileprivate struct NavigateModifier<SomeView: View>: ViewModifier {

    // MARK: Private properties
    fileprivate let destination: SomeView
    @Binding fileprivate var binding: Bool


    // MARK: - View body
    fileprivate func body(content: Content) -> some View {
        NavigationView {
            ZStack {
                content
                    .navigationBarTitle("")
                    .navigationBarHidden(true)
                NavigationLink(destination: destination
                    .navigationBarTitle("")
                    .navigationBarHidden(true),
                               isActive: $binding) {
                    EmptyView()
                }
            }
        }
    }
}

또는 정적 함수 확장을 사용할 수도 있습니다.예를 들어 제목 표시줄을 텍스트로 확장합니다.이를 통해 코드를 재사용하기가 매우 쉽습니다.

이 경우 뷰 클로저가 있는 @Viewbuilder 래퍼를 전달하여 뷰에 맞는 사용자 지정 유형을 반환할 수 있습니다.예를 들어,

import SwiftUI

extension Text{
    static func titleBar<Content:View>(
        titleString:String,
        @ViewBuilder customIcon: ()-> Content
    )->some View {
        HStack{
            customIcon()
            Spacer()
            Text(titleString)
                .font(.title)
            Spacer()
        }
        
    }
}

struct Text_Title_swift_Previews: PreviewProvider {
    static var previews: some View {
        Text.titleBar(titleString: "title",customIcon: {
            Image(systemName: "arrowshape.turn.up.backward")
        })
            .previewLayout(.sizeThatFits)
    }
}


다른 보기에 두 개의 다른 보기를 전달하려고 하는데 이 오류 때문에 이 보기를 수행할 수 없는 경우:

식에 대한 진단을 생성하지 못했습니다. 버그 보고서를 제출하십시오.

<Content:처음 통과한 보기 >입니다. 처음 통과한 보기는 해당 유형을 저장하고, 통과 중인 두 번째 보기는 동일한 유형이어야 합니다. 이렇게 하면 텍스트와 이미지를 통과하려는 경우에는 통과할 수 없습니다.

솔루션은 간단합니다. 다른 컨텐츠 보기를 추가하고 이름을 다르게 지정합니다.

예:

struct Collapsible<Title: View, Content: View>: View {
@State var title: () -> Title
@State var content: () -> Content

@State private var collapsed: Bool = true

var body: some View {
    VStack {
        Button(
            action: { self.collapsed.toggle() },
            label: {
                HStack {
                    self.title()
                    Spacer()
                    Image(systemName: self.collapsed ? "chevron.down" : "chevron.up")
                }
                .padding(.bottom, 1)
                .background(Color.white.opacity(0.01))
            }
        )
        .buttonStyle(PlainButtonStyle())
        
        VStack {
            self.content()
        }
        .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: collapsed ? 0 : .none)
        .clipped()
        .animation(.easeOut)
        .transition(.slide)
    }
}

}

보기 호출:

Collapsible {
            Text("Collapsible")
        } content: {
                ForEach(1..<5) { index in
                    Text("\(index) test")
                }
        }

2개 보기에 대한 구문

struct PopOver<Content, PopView> : View where Content: View, PopView: View {
var isShowing: Bool
@ViewBuilder var content: () -> Content
@ViewBuilder var popover: () -> PopView

var body: some View {
    ZStack(alignment: .center) {
        self
            .content()
            .disabled(isShowing)
            .blur(radius: isShowing ? 3 : 0)
        
        ZStack {
            self.popover()
        }
        .frame(width: 112, height: 112)
        .opacity(isShowing ? 1 : 0)
        .disabled(!isShowing)
        
    }
}

}

언급URL : https://stackoverflow.com/questions/56938805/how-to-pass-one-swiftui-view-as-a-variable-to-another-view-struct

반응형