为什么 NavigationLink 按钮在自定义 UIViewControllerRepresentable 包装器中显示为“禁用”

Osa*_*eem 5 scrollview uiscrollview uiviewcontroller swiftui navigationlink

我创建了一个符合 UIViewControllerRepresentable 的包装器。我创建了一个 UIViewController,其中包含一个启用了分页的 UIScrollView。自定义包装器可以正常工作。

SwiftyUIScrollView(.horizontal, pagingEnabled: true) {
      NavigationLink(destination: Text("This is a test")) {
             Text("Navigation Link Test")
      }
}
Run Code Online (Sandbox Code Playgroud)

此按钮显示为禁用和灰色。单击它没有任何作用。但是,如果将同一个按钮放在 ScrollView {} 包装器中,则它可以工作。

我在这里错过了什么。这是自定义滚动视图类代码:

enum DirectionX {
case horizontal
case vertical
}


struct SwiftyUIScrollView<Content: View>: UIViewControllerRepresentable {
var content: () -> Content
var axis: DirectionX
var numberOfPages = 0
var pagingEnabled: Bool = false
var pageControlEnabled: Bool = false
var hideScrollIndicators: Bool = false

init(axis: DirectionX, numberOfPages: Int, pagingEnabled: Bool, 
 pageControlEnabled: Bool, hideScrollIndicators: Bool, @ViewBuilder content: 
 @escaping () -> Content) {
    self.content = content
    self.numberOfPages = numberOfPages
    self.pagingEnabled = pagingEnabled
    self.pageControlEnabled = pageControlEnabled
    self.hideScrollIndicators = hideScrollIndicators
    self.axis = axis
}

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

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

class UIScrollViewController: UIViewController, UIScrollViewDelegate {

var axis: DirectionX = .horizontal
var numberOfPages: Int = 0
var pagingEnabled: Bool = false
var pageControlEnabled: Bool = false
var hideScrollIndicators: Bool = false

lazy var scrollView: UIScrollView = {
    let view = UIScrollView()
    view.delegate = self
    view.isPagingEnabled = pagingEnabled
    view.showsVerticalScrollIndicator = !hideScrollIndicators
    view.showsHorizontalScrollIndicator = !hideScrollIndicators
    return view
}()

lazy var pageControl : UIPageControl = {
    let pageControl = UIPageControl()
        pageControl.numberOfPages = numberOfPages
        pageControl.currentPage = 0
        pageControl.tintColor = UIColor.white
        pageControl.pageIndicatorTintColor = UIColor.gray
        pageControl.currentPageIndicatorTintColor = UIColor.white
        pageControl.translatesAutoresizingMaskIntoConstraints = false
        pageControl.isHidden = !pageControlEnabled
    return pageControl
}()


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

override func viewDidLoad() {
    super.viewDidLoad()

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

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

    view.addSubview(pageControl)
    pageControl.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -50).isActive = true
    pageControl.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    pageControl.heightAnchor.constraint(equalToConstant: 60).isActive = true
    pageControl.widthAnchor.constraint(equalToConstant: 200).isActive = true
}

func makefullScreen(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),
      ])
  }

   func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {

        let currentIndexHorizontal = round(scrollView.contentOffset.x / self.view.frame.size.width)
        let currentIndexVertical = round(scrollView.contentOffset.y / self.view.frame.size.height)

    switch axis {
    case .horizontal:
         self.pageControl.currentPage = Int(currentIndexHorizontal)
        break
    case .vertical:
        self.pageControl.currentPage = Int(currentIndexVertical)
        break
    default:
        break
    }

 }

 }
Run Code Online (Sandbox Code Playgroud)

更新

这就是我使用包装器的方式:

struct TestData {
var id : Int
var text: String
}


struct ContentView: View {
var contentArray: [TestData] = [TestData(id: 0, text: "Test 1"), TestData(id: 1, text: "Test 2"), TestData(id: 2, text: "TEst 3"), TestData(id: 4, text: "Test 4")]


var body: some View {
    NavigationView {
      GeometryReader { g in
        ZStack{
        SwiftyUIScrollView(axis: .horizontal, numberOfPages: self.contentArray.count, pagingEnabled: true, pageControlEnabled: true, hideScrollIndicators: true) {
                    HStack(spacing: 0) {
                        ForEach(self.contentArray, id: \.id) { item in
                            TestView(data: item)
                                .frame(width: g.size.width, height: g.size.height)
                        }
                    }
            }.frame(width: g.size.width)
            }.frame(width: g.size.width, height: g.size.height)
            .navigationBarTitle("Test")
        }
    }
}
}

struct TestView: View {
var data: TestData
var body: some View {
    GeometryReader { g in
            VStack {
                HStack {
                    Spacer()
                }
                Text(self.data.text)
                Text(self.data.text)

                VStack {
                    NavigationLink(destination: Text("This is a test")) {
                                   Text("Navigation Link Test")
                    }
                }
                Button(action: {
                    print("Do something")
                }) {
                    Text("Button")
                }
            }.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
            .background(Color.yellow)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

“导航链接测试”按钮变灰。 在此处输入图片说明

kon*_*iki 9

我花了一些时间在你的代码上。我想我明白问题是什么,并找到了解决方法。

我认为,问题是NavigationLink要启用,它需要在NavigationView. 尽管您是这样,但似乎与UIHostingController. 如果您检查UIHostingController.navigationController,您会看到它为零。

我能想到的唯一解决方案是在可以手动触发的NavigationLink外部隐藏SwiftyUIScrollView(使用其isActive参数)。然后在您的 中SwiftyUIScrollView,您应该使用一个简单的按钮,当点击该按钮时,会更改您的模型以切换NavigationLink's isActive绑定。下面是一个似乎工作正常的例子。

注意NavigationLinkisActive有,此刻一个小错误,但它可能会尽快修复。要了解更多信息:https : //swiftui-lab.com/bug-navigationlink-isactive/

window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(MyModel()))
Run Code Online (Sandbox Code Playgroud)
import SwiftUI

class MyModel: ObservableObject {
    @Published var navigateNow = false
}

struct TestData {
    var id : Int
    var text: String
}


struct ContentView: View {
    @EnvironmentObject var model: MyModel

    var contentArray: [TestData] = [TestData(id: 0, text: "Test 1"), TestData(id: 1, text: "Test 2"), TestData(id: 2, text: "TEst 3"), TestData(id: 4, text: "Test 4")]


    var body: some View {
        NavigationView {
            GeometryReader { g in
                ZStack{
                    NavigationLink(destination: Text("Destination View"), isActive: self.$model.navigateNow) { EmptyView() }

                    SwiftyUIScrollView(axis: .horizontal, numberOfPages: self.contentArray.count, pagingEnabled: true, pageControlEnabled: true, hideScrollIndicators: true) {
                        HStack(spacing: 0) {
                            ForEach(self.contentArray, id: \.id) { item in
                                TestView(data: item)
                                    .frame(width: g.size.width, height: g.size.height)
                            }
                        }
                    }.frame(width: g.size.width)
                }.frame(width: g.size.width, height: g.size.height)
                    .navigationBarTitle("Test")
            }
        }
    }
}

struct TestView: View {
    @EnvironmentObject var model: MyModel

    var data: TestData
    var body: some View {

        GeometryReader { g in
            VStack {
                HStack {
                    Spacer()
                }
                Text(self.data.text)
                Text(self.data.text)

                VStack {
                    Button("Pseudo-Navigation Link Test") {
                        self.model.navigateNow = true
                    }
                }
                Button(action: {
                    print("Do something")
                }) {
                    Text("Button")
                }
            }.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
                .background(Color.yellow)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

另一件事是您使用AnyView. 它带来了沉重的性能价格。建议您仅使用AnyView叶视图(不是您的情况)。所以我确实设法重构了您的代码以消除AnyView. 往下看,希望有帮助。

import SwiftUI

enum DirectionX {
    case horizontal
    case vertical
}


struct SwiftyUIScrollView<Content: View>: UIViewControllerRepresentable {
    var content: () -> Content
    var axis: DirectionX
    var numberOfPages = 0
    var pagingEnabled: Bool = false
    var pageControlEnabled: Bool = false
    var hideScrollIndicators: Bool = false

    init(axis: DirectionX, numberOfPages: Int,
         pagingEnabled: Bool,
         pageControlEnabled: Bool,
         hideScrollIndicators: Bool,
         @ViewBuilder content: @escaping () -> Content) {

        self.content = content
        self.numberOfPages = numberOfPages
        self.pagingEnabled = pagingEnabled
        self.pageControlEnabled = pageControlEnabled
        self.hideScrollIndicators = hideScrollIndicators
        self.axis = axis
    }

    func makeUIViewController(context: Context) -> UIScrollViewController<Content> {
        let vc = UIScrollViewController(rootView: self.content())
        vc.axis = axis
        vc.numberOfPages = numberOfPages
        vc.pagingEnabled = pagingEnabled
        vc.pageControlEnabled = pageControlEnabled
        vc.hideScrollIndicators = hideScrollIndicators
        return vc
    }

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

class UIScrollViewController<Content: View>: UIViewController, UIScrollViewDelegate {

    var axis: DirectionX = .horizontal
    var numberOfPages: Int = 0
    var pagingEnabled: Bool = false
    var pageControlEnabled: Bool = false
    var hideScrollIndicators: Bool = false

    lazy var scrollView: UIScrollView = {
        let view = UIScrollView()
        view.delegate = self
        view.isPagingEnabled = pagingEnabled
        view.showsVerticalScrollIndicator = !hideScrollIndicators
        view.showsHorizontalScrollIndicator = !hideScrollIndicators
        return view
    }()

    lazy var pageControl : UIPageControl = {
        let pageControl = UIPageControl()
        pageControl.numberOfPages = numberOfPages
        pageControl.currentPage = 0
        pageControl.tintColor = UIColor.white
        pageControl.pageIndicatorTintColor = UIColor.gray
        pageControl.currentPageIndicatorTintColor = UIColor.white
        pageControl.translatesAutoresizingMaskIntoConstraints = false
        pageControl.isHidden = !pageControlEnabled
        return pageControl
    }()

    init(rootView: Content) {
        self.hostingController = UIHostingController<Content>(rootView: rootView)
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    var hostingController: UIHostingController<Content>! = nil

    override func viewDidLoad() {
        super.viewDidLoad()

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

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

        view.addSubview(pageControl)
        pageControl.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -50).isActive = true
        pageControl.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        pageControl.heightAnchor.constraint(equalToConstant: 60).isActive = true
        pageControl.widthAnchor.constraint(equalToConstant: 200).isActive = true
    }

    func makefullScreen(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),
        ])
    }

    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {

        let currentIndexHorizontal = round(scrollView.contentOffset.x / self.view.frame.size.width)
        let currentIndexVertical = round(scrollView.contentOffset.y / self.view.frame.size.height)

        switch axis {
        case .horizontal:
            self.pageControl.currentPage = Int(currentIndexHorizontal)
            break
        case .vertical:
            self.pageControl.currentPage = Int(currentIndexVertical)
            break
        default:
            break
        }

    }

}
Run Code Online (Sandbox Code Playgroud)