如何处理 UIViewControllerRepresentable 包装器中的 NavigationLink?
Posted
技术标签:
【中文标题】如何处理 UIViewControllerRepresentable 包装器中的 NavigationLink?【英文标题】:How to handle NavigationLink inside UIViewControllerRepresentable wrapper? 【发布时间】: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),
])
【问题讨论】:
我也有同样的问题,你有什么进展吗? 关于这个问题有什么进展吗?? 试试我写的解决方案,肯定行得通。 【参考方案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 的情况下,您可能会使用包含 destination
和 is_active
变量的共享 ObservableObject。您可以将 2 NavigationLinks 更改为 Buttons,让操作方法更改 ObservableObject 的 destination
和 is_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。以上是关于如何处理 UIViewControllerRepresentable 包装器中的 NavigationLink?的主要内容,如果未能解决你的问题,请参考以下文章