【问题标题】:How to handle NavigationLink inside UIViewControllerRepresentable wrapper?如何处理 UIViewControllerRepresentable 包装器中的 NavigationLink?
【发布时间】:2019-12-21 00:32:32
【问题描述】:

所以我正在尝试创建一个自定义分页滚动视图。我已经能够创建该包装器,并且该包装器内的内容由一个自定义视图组成。在该自定义视图中,我有两个 NavigationLink 按钮,按下时应该将用户带到两个不同的视图。

那些 NavigationLink 按钮不起作用。

scrollViewWrapper 在 NavigationView 中。我创建了一个测试按钮,它只是一个简单的按钮,似乎可以工作。所以我在 NavigationLink 和自定义 UIViewControllerRepresentable 上做的不对。

这是我使用自定义包装器的地方。

NavigationView {
            UIScrollViewWrapper {
                HStack(spacing: 0) {
                    ForEach(self.onboardingDataArray, id: \.id) { item in
                          OnboardingView(onboardingData: item)
                                .frame(width: geometry.size.width, height: geometry.size.height)
                       }
                    }
            }.frame(width: geometry.size.width, height: geometry.size.height)
             .background(Color.blue)
           }

入职视图:

struct OnboardingView: View {
var onboardingData: OnboardingModel

var body: some View {
    GeometryReader { geometry in
        VStack(spacing: 10) {
            Spacer()
            Image("\(self.onboardingData.image)")
                .resizable()
                .frame(width: 300, height: 300)
                .aspectRatio(contentMode: ContentMode.fit)
                .clipShape(Circle())
                .padding(20)

            Text("\(self.onboardingData.titleText)")
                .frame(width: geometry.size.width, height: 20, alignment: .center)
                .font(.title)

            Text("\(self.onboardingData.descriptionText)")
                .lineLimit(nil)
                .padding(.leading, 15)
                .padding(.trailing, 15)
                .font(.system(size: 16))
                .frame(width: geometry.size.width, height: 50, alignment: .center)
                .multilineTextAlignment(.center)
            Spacer(minLength: 20)
            if self.onboardingData.showButton ?? false {
                VStack {
                    Button(action: {
                        print("Test")
                    }) {
                        Text("Test Button")
                    }
                    NavigationLink(destination: LogInView()) {
                        Text("Login!")
                    }
                    NavigationLink(destination: SignUpView()) {
                        Text("Sign Up!")
                    }
                }
            }

            Spacer()
        }
    }
}
    }

自定义 ScrollView Wrapper 代码:

struct UIScrollViewWrapper<Content: View>: UIViewControllerRepresentable {
var content: () -> Content

init(@ViewBuilder content: @escaping () -> Content) {
    self.content = content
}

func makeUIViewController(context: Context) -> UIScrollViewController {
    let vc = UIScrollViewController()
    vc.hostingController.rootView = AnyView(self.content())
    return vc
}

func updateUIViewController(_ viewController: UIScrollViewController, context: Context) {
    viewController.hostingController.rootView = AnyView(self.content())
  }
}

class UIScrollViewController: UIViewController {

lazy var scrollView: UIScrollView = {
    let view = UIScrollView()
    view.isPagingEnabled = true
    return view
}()

var hostingController: UIHostingController<AnyView> = UIHostingController(rootView: AnyView(EmptyView()))

override func viewDidLoad() {
    super.viewDidLoad()

    self.view.addSubview(self.scrollView)
    self.pinEdges(of: self.scrollView, to: self.view)

    self.hostingController.willMove(toParent: self)
    self.scrollView.addSubview(self.hostingController.view)
    self.pinEdges(of: self.hostingController.view, to: self.scrollView)
    self.hostingController.didMove(toParent: self)
}

func pinEdges(of viewA: UIView, to viewB: UIView) {
      viewA.translatesAutoresizingMaskIntoConstraints = false
      viewB.addConstraints([
          viewA.leadingAnchor.constraint(equalTo: viewB.leadingAnchor),
          viewA.trailingAnchor.constraint(equalTo: viewB.trailingAnchor),
          viewA.topAnchor.constraint(equalTo: viewB.topAnchor),
          viewA.bottomAnchor.constraint(equalTo: viewB.bottomAnchor),
      ])
  }

【问题讨论】:

  • 我也有同样的问题,你有什么进展吗?
  • 关于这个问题有什么进展吗??
  • 试试我写的解决方案,肯定行得通。

标签: ios swift swiftui navigationlink


【解决方案1】:

这是因为您使用 UIViewControllerRepresentable 而不是 UIViewRepresentable。我猜 UIScrollViewController 会阻止目标控制器被当前控制器呈现。

试试上面的代码:

import UIKit
import SwiftUI

struct ScrollViewWrapper<Content>: UIViewRepresentable where Content: View{
    func updateUIView(_ uiView: UIKitScrollView, context: UIViewRepresentableContext<ScrollViewWrapper<Content>>) {

    }


    typealias UIViewType = UIKitScrollView

    let content: () -> Content
    var showsIndicators : Bool

    public init(_ axes: Axis.Set = .vertical, showsIndicators: Bool = true, @ViewBuilder content: @escaping () -> Content) {
        self.content = content
        self.showsIndicators = showsIndicators

    }

    func makeUIView(context: UIViewRepresentableContext<ScrollViewWrapper>) -> UIViewType {
        let hosting = UIHostingController(rootView: AnyView(content()))
        let width = UIScreen.main.bounds.width
        let size = hosting.view.sizeThatFits(CGSize(width: width, height: CGFloat.greatestFiniteMagnitude))
        hosting.view.frame = CGRect(x: 0, y: 0, width: width, height: size.height)
        let view = UIKitScrollView()
        view.delegate = view
        view.alwaysBounceVertical = true
        view.addSubview(hosting.view)
        view.contentSize = CGSize(width: width, height: size.height)
        return view
    }

}


class UIKitScrollView: UIScrollView, UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        print(scrollView.contentOffset) // Do whatever you want.
    }
}

【讨论】:

    【解决方案2】:

    这是对上述解决方案的扩展,从不滚动内部内容。

    我遇到了类似的问题。我发现问题出在 UIViewControllerRepresentable 上。而是使用 UIViewRepresentable,虽然我不确定问题是什么。我能够使用下面的代码获得导航链接的工作。

    struct SwiftyUIScrollView<Content>: UIViewRepresentable where Content: View {
    typealias UIViewType = Scroll
    
    var content: () -> Content
    var pagingEnabled: Bool = false
    var hideScrollIndicators: Bool = false
    @Binding var shouldUpdate: Bool
    @Binding var currentIndex: Int
    
    var onScrollIndexChanged: ((_ index: Int) -> Void)
    
    public init(pagingEnabled: Bool,
                hideScrollIndicators: Bool,
                currentIndex: Binding<Int>,
                shouldUpdate: Binding<Bool>,
                @ViewBuilder content: @escaping () -> Content, onScrollIndexChanged: @escaping ((_ index: Int) -> Void)) {
        self.content = content
        self.pagingEnabled = pagingEnabled
        self._currentIndex = currentIndex
        self._shouldUpdate = shouldUpdate
        self.hideScrollIndicators = hideScrollIndicators
        self.onScrollIndexChanged = onScrollIndexChanged
    }
    
    func makeUIView(context: UIViewRepresentableContext<SwiftyUIScrollView>) -> UIViewType {
        let hosting = UIHostingController(rootView: content())
        let view = Scroll(hideScrollIndicators: hideScrollIndicators, isPagingEnabled: pagingEnabled)
        view.scrollDelegate = context.coordinator
        view.alwaysBounceHorizontal = true
        view.addSubview(hosting.view)
        makefullScreen(of: hosting.view, to: view)
        return view
    }
    
    class Coordinator: NSObject, ScrollViewDelegate {
        func didScrollToIndex(_ index: Int) {
            self.parent.onScrollIndexChanged(index)
        }
    
        var parent: SwiftyUIScrollView
    
        init(_ parent: SwiftyUIScrollView) {
            self.parent = parent
        }
    }
    
    func makeCoordinator() -> SwiftyUIScrollView<Content>.Coordinator {
        Coordinator(self)
    }
    
    func updateUIView(_ uiView: Scroll, context: UIViewRepresentableContext<SwiftyUIScrollView<Content>>) {
        if shouldUpdate {
            uiView.scrollToIndex(index: currentIndex)
        }
    }
    
    func makefullScreen(of childView: UIView, to parentView: UIView) {
        childView.translatesAutoresizingMaskIntoConstraints = false
        childView.leftAnchor.constraint(equalTo: parentView.leftAnchor).isActive = true
        childView.rightAnchor.constraint(equalTo: parentView.rightAnchor).isActive = true
        childView.topAnchor.constraint(equalTo: parentView.topAnchor).isActive = true
        childView.bottomAnchor.constraint(equalTo: parentView.bottomAnchor).isActive = true
    }
    }
    

    然后创建一个新类来处理滚动视图的委托。您也可以将以下代码包含在 UIViewRepresentable 中。但我更喜欢将其分开以获得干净的代码。

    class Scroll: UIScrollView, UIScrollViewDelegate {
    
    var hideScrollIndicators: Bool = false
    var scrollDelegate: ScrollViewDelegate?
    var tileWidth = 270
    var tileMargin = 20
    
    init(hideScrollIndicators: Bool, isPagingEnabled: Bool) {
        super.init(frame: CGRect.zero)
        showsVerticalScrollIndicator = !hideScrollIndicators
        showsHorizontalScrollIndicator = !hideScrollIndicators
        delegate = self
        self.isPagingEnabled = isPagingEnabled
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        let currentIndex = scrollView.contentOffset.x / CGFloat(tileWidth+tileMargin)
        scrollDelegate?.didScrollToIndex(Int(currentIndex))
    }
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let currentIndex = scrollView.contentOffset.x / CGFloat(tileWidth+tileMargin)
        scrollDelegate?.didScrollToIndex(Int(currentIndex))
    }
    
    func scrollToIndex(index: Int) {
        let newOffSet = CGFloat(tileWidth+tileMargin) * CGFloat(index)
        contentOffset = CGPoint(x: newOffSet, y: contentOffset.y)
    }
    }
    

    现在使用下面的代码来实现滚动视图。

    @State private var activePageIndex: Int = 0
    @State private var shouldUpdateScroll: Bool = false
    
    SwiftyUIScrollView(pagingEnabled: false, hideScrollIndicators: true, currentIndex: $activePageIndex, shouldUpdate: $shouldUpdateScroll, content: {
                HStack(spacing: 20) {
                    ForEach(self.data, id: \.id) { data in
                        NavigationLink(destination: self.getTheNextView(data: data)) {
                            self.cardView(data: data)
                        }
                    }
                }
                .padding(.horizontal, 30.0)
            }, onScrollIndexChanged: { (newIndex) in
               shouldUpdateScroll = false
               activePageIndex = index
                // Your own required handling
            })
    
    
    func getTheNextView(data: Any) -> AnyView {
        // Return the required destination View
    }
    

    【讨论】:

      【解决方案3】:

      正如其他答案所述,将 NavigationLink 放在 UIViewControllerRepresentable 中存在问题。

      我通过将 UIViewControllerRepresentable 和 NavigationLink 包装在 View 中并以编程方式从 UIViewControllerRepresentable 内部激活 NavigationLink 解决了这个问题。

      例如:

      struct MyView: View
      {        
          @State var destination: AnyView? = nil
          @State var is_active: Bool = false
      
          var body: some View
          {
              ZStack
              {
                  MyViewControllerRepresentable( self )
      
                  NavigationLink( destination: self.destination, isActive: self.$is_active )
                  {
                      EmptyView()
                  }
              }
          }
      
          func goTo( destination: AnyView )
         {
              self.destination = destination
              self.is_active = true
         }
      }
      

      在我的例子中,我将 MyView 实例传递给我的 MyViewControllerRepresentable 正在包装的 UIViewController,并在单击按钮时调用我的 goTo(destination:AnyView) 方法。

      我们的案例之间的区别在于我的 UIViewController 是我自己用 UIKit 编写的类(与 UIHostingController 相比)。在您使用 UIHostingController 的情况下,您可能会使用包含 destinationis_active 变量的共享 ObservableObject。您可以将 2 NavigationLinks 更改为 Buttons,让操作方法更改 ObservableObject 的 destinationis_active 变量。

      【讨论】:

        【解决方案4】:

        不要忘记将您的主机控制器添加为孩子。

        override func viewDidLoad() {
            super.viewDidLoad()
        
            self.view.addSubview(self.scrollView)
            self.pinEdges(of: self.scrollView, to: self.view)
            addChild(self.hostingController)
            self.hostingController.willMove(toParent: self)
            self.scrollView.addSubview(self.hostingController.view)
            self.pinEdges(of: self.hostingController.view, to: self.scrollView)
            self.hostingController.didMove(toParent: self)
        }
        

        【讨论】:

          【解决方案5】:

          您是否设置了触发转场?如果您使用的是 Xcode,您可以右键单击您在主情节提要中创建的按钮。如果未设置,您可以转到右上角侧边栏上的连接检查器,您可以在其中找到文件检查器、身份检查器、属性检查器...并指定您希望按钮执行的操作。

          【讨论】:

          • 我这里使用的是 SwiftUI。
          猜你喜欢
          • 2019-12-21
          • 1970-01-01
          • 2021-10-18
          • 1970-01-01
          • 2011-03-06
          • 2018-01-19
          • 2019-05-26
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多