祝大家有美好的一天!有了你,我,安娜Zharkova,领先的移动开发者Usetech。现在,让我们来谈谈在SwiftUI,导航另一个有趣的点。如果您错过了该系列中的前几篇文章,可以在这里阅读:第1部分第2部分第3部分随着可视部分说明的更改和向声明性语法的过渡,SwiftUI应用程序中的导航控件也已更改。直接拒绝使用UIViewContoller;不直接使用UINavigationController。它由NavigationView代替。@available(iOS 13.0, OSX 10.15, tvOS 13.0, *)
@available(watchOS, unavailable)
public struct NavigationView<Content> : View where Content : View {
public init(@ViewBuilder content: () -> Content)
}
本质上是UINavigationController及其功能的包装。
主要的转换机制是NavigationLink(segue的类似物),它在主体View代码中立即设置。public struct NavigationLink<Label, Destination> : View where Label : View,
Destination : View {
.
public init(destination: Destination, @ViewBuilder label: () -> Label)
public init(destination: Destination, isActive: Binding<Bool>,
@ViewBuilder label: () -> Label)
public init<V>(destination: Destination, tag: V, selection: Binding<V?>,
@ViewBuilder label: () -> Label) where V : Hashable
}
创建NavigationLink时,它指示进行过渡的视图以及NavigationLink包装的视图,即与之交互时,会激活NavigationLink。有关Apple文档中初始化NavigationLink的可能方式的更多信息,但是,请记住,由于封装的原因,无法直接访问View堆栈,只能对导航进行编程,只能返回一级返回,然后通过封装的代码返回“后退”按钮
同样在SwiftUI中,没有动态软件导航。如果过渡不与触发事件(例如,单击按钮)相关,而是由于某种逻辑而跟随,则不要这样做。到下一个View的过渡必须绑定到NavigationLink机制,在描述包含它们的View时立即以声明方式设置。所有。如果我们的屏幕应该包含到许多不同屏幕的过渡,那么代码变得很麻烦: NavigationView{
NavigationLink(destination: ProfileView(), isActive: self.$isProfile) {
Text("Profile")
}
NavigationLink(destination: Settings(), isActive: self.$isSettings) {
Text("Settings")
}
NavigationLink(destination: Favorite(), isActive: self.$isFavorite) {
Text("Favorite")
}
NavigationLink(destination: Login(), isActive: self.$isLogin) {
Text("Login")
}
NavigationLink(destination: Search(), isActive: self.$isSearch) {
Text("Search")
}
}
我们可以通过几种方式管理链接:-通过@Binding属性控制NavigationLink活动 NavigationLink(destination: ProfileView(), isActive: self.$isProfile) {
Text("Profile")
}
-通过条件(状态变量)控制链接的创建 if self.isProfile {
NavigationLink(destination: ProfileView()) {
Text("Profile")
}
}
第一种方法为我们增加了监视控制变量状态的工作。如果我们计划向前导航超过1个级别,那么这是一项非常困难的任务。对于类似元素列表的屏幕,一切看起来都很紧凑: NavigationView{
List(model.data) { item in
NavigationLink(destination: NewsItemView(item:item)) {
NewsItemRow(data: item)
}
}
我认为NavigationLink的最严重问题是指定的所有View链接都不是惰性的。它们不是在链接触发时创建的,而是在创建时创建的。如果我们有许多元素的列表或到许多不同的View大量内容的过渡,那么这不会以最佳方式影响我们应用程序的性能。如果我们仍然使用逻辑将ViewModel附加到这些View,在实现中未考虑或未正确考虑生命周期View,则情况将变得非常困难。例如,我们有一个新闻列表,其中包含相同类型的元素。我们尚未浏览过单个新闻的单个屏幕,并且模型已经挂在我们的记忆中:
在这种情况下,我们可以做些什么来使我们的生活更轻松?首先,回想一下View不存在于真空中,而是在UIHostingController中呈现。open class UIHostingController<Content> : UIViewController where Content : View {
public init(rootView: Content)
public var rootView: Content
}
这就是UIViewController。因此,我们可以执行以下操作。我们将转移到新UIHostingController内的下一个View的所有责任转移到当前View的控制器。让我们创建将在View中调用的导航和配置模块。使用UIViewController的导航器将如下所示:class Navigator {
private init(){}
static let shared = Navigator()
private weak var view: UIViewController?
internal weak var nc: UINavigationController?
func setup(view: UIViewController) {
self.view = view
}
internal func open<Content:View>(screen: Content.Type, _ data: Any? = nil) {
if let vc = ModuleConfig.shared.config(screen: screen)?
.createScreen(data) {
self.nc?.pushViewController(vc, animated: true)
}
}
按照相同的原则,我们将创建一个配置器工厂,这将为我们提供特定模块的配置器的实现:protocol IConfugator: class {
func createScreen(_ data: Any?)->UIViewController
}
class ModuleConfig{
private init(){}
static let shared = ModuleConfig()
func config<Content:View>(screen: Content.Type)->IConfugator? {
if screen == NewsListView.self {
return NewsListConfigurator.shared
}
return nil
}
}
导航器根据屏幕类型询问特定模块的配置器,并将所有必要的信息传递给它。class NewsListConfigurator: IConfugator {
static let shared = NewsListConfigurator()
func createScreen(_ data: Any?) -> UIViewController {
var view = NewsListView()
let presenter = NewsListPresenter()
let interactor = NewsListInteractor()
interactor.output = presenter
presenter.output = view
view.output = interactor
let vc = UIHostingController<ContainerView<NewsListView>>
(rootView: ContainerView(content: view))
return vc
}
}
配置器提供了UIViewController,它是导航器,并将UINavigationController推送到共享堆栈上。
将代码中的NavigationLink替换为对Navigator的调用。作为触发,我们将有一个单击列表项的事件: List(model.data) { item in
NewsItemRow(data: item)
.onTapGesture {
Navigator.shared.open(screen: NewsItemView.self, item)
}
}
没有什么可以阻止我们以相同的方式以任何View方法调用Navigator的。不只是在体内。除了代码明显变得更清晰的事实之外,我们还卸载了内存。毕竟,使用这种方法,只有在调用时才创建视图。
现在,我们的SwiftUI应用程序更易于扩展和修改。代码干净漂亮。您可以在此处找到示例代码。下次,我们将讨论更深层的Combine实施。