SwiftUI: Navigation all know

2023-11-06 12:40
文章标签 swiftui know navigation

本文主要是介绍SwiftUI: Navigation all know,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Here is the repo’s url: https://gitee.com/kmyhy/SwiftUINavigation/

In normal UIKit, we use UINavigationController to make the navigaion within App. But in SwiftUI, we can’t make good use of UINavigationController anyway.

Actually, SwiftUI has a totally different navigation system which include NavigationView - just like an alternative to UINavigationController - and NavigationLink - just like a kind of UIButton which allow you jump to another view.

Today, let’s have a look how to use them step by step.

Here we go!

Demo app & NavigationView

Create a new SwiftUI project in Xcode, rename the ContentView.swift with FirstView.swift. First of all, we need other views which we want to jump to, so create a new View called SecondView and another View ThirdView. For now, we no need to care about their details. No worries, we will update them afterwards.

If we want to jump to SecondView from FirstView, and from SecondView to another View, we need to set FirstView as root view of NavigationView. In FirstView.swift, we need to update the definition ofbody:

var body: some View {NavigationView{VStack{Button("2nd view"){}}}.navigationViewStyle(StackNavigationViewStyle())}

Please always use ‘.navigationViewStyle(StackNavigationViewStyle())’ to you NavigationView, or else the result won’t be what you expect.

Build & run. You can see a button set in the middle of the screen, but it doesn’t anything when you click it:

You used a NavigationView as the root view of FirstView, and it wrapped all of view’s body up. It literally set the FirstView as root view of NavigationView just like you did in UIKit before which you set a view controller as root view controller of an UINavigationController.

But NavigationView only works with NavigationLink together. A button isn’t sensible here. Let’s replace it with NavigationLink.

But, hold on, before you do that, you need a responsive data object or an environment object. What?

Environment Object

Unlike UIKit, SwiftUI use Data-Driven mechanism instead of Event-Driven mechanism. Instead of you manually push a view controlle to UINavigationController’s viewcontroller stack, you actually need change a little bit Data’s status or membership variable. Seeing as these statuses of navigation system always have been saved as global variables so that all views can access them, they absolutely should be seen as an ‘environment object’.

Define environment object

We define this enviroment objest first:

//
//  Navigation.swift
//  NavigationDemo
//
//  Created by hongyan.yang on 2022/5/10.
//
import SwiftUIclass Navigation: ObservableObject {@Published var secondViewShowing = false
}

Navigation should be a class, never be a struct because it conformed ObservableObject protocol. ObservableObject is a kind of object which should be considered as responsive object in Vue.js or React.js. It contains some observable members that they can be listened by outside listeners and listeners will get notices as long as their values are modified.

We used a bool (secondViewShowing) denote whether SecondView should be shown in screen. @Published is a property decorator in Swift, it says this property will be bound by some special operations such as sending a notice to all listner or triggering a UI refreshment.

We needn’t to define a bool which denote FirstView’s status of appearing cuz it’s a root view. A root view always stay here, it shows when other views disappear , otherwise, it’s overlay by other view.

Inject environment object

The environment object is named the environment object because it’s managed by SwiftUI Container. Just like spring IoC, before we can use it we need to inject it into the SwiftUI Container.

We use environmentObject modifiers to inject an enviroment object, In App.swift :

var body: some Scene {WindowGroup {FirstView().environmentObject(Navigation())}}

We create a Navigation instance and inject it to environment variabs.

Environment object must be supplied by an ancestor view . So we can access the enviroment object on FirstView.

This lets us share data among all of views, also make our views automatically stay updated when data is changed.

NavigationLink

We will create a 3 page’s navigation stack, like this:

FirstView -> SecondView -> ThirdView

Firstly, we take the first step: FirstView -> SecondView

Make the first navigation

Let’s replace Button with NavigationLink.

 	NavigationLink(destination: SecondView(), isActive: $navigation.secondViewShowing) {Text("2nd view")}.navigationBarTitle("first view")

NavigationLink() has 3 parameters:

  • the first one is the Next Screen View which should be navigate to.
  • the second one is an observable variable which should be bound to specific property of an environment object. Once we did that, we can use this property to show/hide the destination view.
  • the last one is a ViewBuilder that can be consider as special closure, we can use it to return a group of views just like you do in this property: var body: some View .

navigationBarTitle() set the navigation bar title of SecondView.

Wait, there’s a error in the line :

Cannot find '$navigation' in scope

That’s beacause we haven’t still declare a local variable refer to our environment object. This required step just acts as spring’s @Autowired annotation except for using the @EnvironmentObject instead of @Autowired:

@EnvironmentObject var navigation: Navigation

We can change SecondView with a little difference:

    var body: some View {VStack{Text("2nd View")}}

Build & run, you can see the FirstView with a navigation title says “first fiew”:

SecondView shows with a navigation bar and back button when you click the NavigationLink ‘2nd view’:

Add more navigations

Let’s update the environment object first. Keep this in mind: now that SwiftUI is Data-Driven, if we want to change anything, then we need to change it’s data at first.

Open Navigation.swift and add a bool to indicate whether the ThirdView is showing or not:

@Published var thirdViewShowing = false

And make some difference to ThirdView:

    @EnvironmentObject var navigation: Navigationvar body: some View {VStack{Text("3rd View")}}

Then we can take the second step: SecondView -> ThirdView.

Update var body in SecondView.swift to:

var body: some View {VStack{Text("2nd View")NavigationLink(destination: ThirdView(), isActive: $navigation.thirdViewShowing) {Text("3rd view")}}
}

Now we added a NavigationLink under the Text:

when you can tap the NavigationLink, the ThirdView appear:

Actually, the thirdViewShowing vairable doesn’t need to stay in Navigation.swift. It isn’t a global variable more than a instance variable because we just use it in SecondView, not in anywhere else. So, we can move it from Navigation struct to SecondView.

Firstly, remove it from Navigation.swift, then add into SecondView:

    @State var thirdViewShowing = false...NavigationLink(destination: ThirdView(), isActive: $thirdViewShowing) {...

You can also let thirdViewShowing stay in original place, but sometimes it can cause some wired issues.

Customize navigation bar

Navigation bar can be costomized. For example, you might want to change navigation title、backbutton outlook and so on.

We can do that by means of a toolbar as a substitue for navigation bar.

struct NavigationToolBar: ViewModifier {var title: String  // 1var rightTitle: String // 2var rightAction: ()->() // 3// 4 init(_ title:String, _ rightTitle:String, _ rightAction:@escaping ()->()){self.title = titleself.rightTitle = rightTitleself.rightAction = rightAction}// 5func body(content: Content) -> some View {content.navigationBarBackButtonHidden(true) // 6.navigationBarTitleDisplayMode(.inline) // 7.toolbar(content: buildToolbar) // 8}// 9@ToolbarContentBuilderfunc buildToolbar() -> some ToolbarContent {// 10ToolbarItem(placement: .principal) {Text(title).font(.system(size: 17, weight: .bold, design: .default))}// 11ToolbarItem(placement: .navigationBarTrailing) {Button(action: {rightAction()}) {Text(rightTitle).font(.system(size: 17, weight: .regular, design: .default))}}}
}

NavigationToolBar conforms protocol ViewModifier, so it should provide body() method.

Let me break down the codes to several parts as below:

  1. the title lies in the middle of toolbar.
  2. the right button’s title lies in the right of toolbar.
  3. The right button’s callback function or closure created for it’s tap event handling.
  4. Init function which initialize 3 members from parameters one by one.
  5. body() function from protocol ViewModifier aims to customize the navigation bar. The parameter content should refer to an ancestor view.
  6. Hide the back button which NavigationBar provide by default.
  7. Disabled the default NavigationBar’s big title display mode.
  8. this modifier replace NavigationBar with a customized toolbar.
  9. buildToolbar() funciton acts as a view builder which returns 1 or many ToolbarItems. A view builder is a special function which returns a collection include 1 or many typed views.
  10. define a ToolbarItem which will be applied to middle text title of toolbar.
  11. define a ToolbarItem which will be applied to right button of toolbar.

To be convenient , we should implement an extension to View:

extension View {func navigationToolBar(_ title:String, _ rightTitle:String, _ rightAction:@escaping ()->()) -> some View {return modifier(NavigationToolBar(title, rightTitle, rightAction))}
}

navigationToolBar extends View in which we call the modifier function with a new NavigationToolBar instance.

modifier function includes some special behaviors. It will pass its ancestor view - in this specific case it’s the SecondView, ThirdView or ForthView - to received parameter i.e. NavigationToolBar instance.

At the meantime modifier() also will call NavigatioinToolBar’s body() when it needs to.

OKay, don’t beat round the bush, cut to the chase. Let’s use this new navigation bar like this:

struct SecondView: View {...var body: some View {VStack{...}.navigationToolBar("SecondView", "back") { // to do}}
}

We called navigationToolBar modifier to customize our navigation bar with a ‘SecondView’ in the middle and a ‘back’ title on the right. Aside from that, we didn’t fulfill the back button’s action.

Likewise, we apply it in ThirdView correspondingly:

struct ThirdView: View {...var body: some View {VStack{...}.navigationToolBar("ThirdView", "back") {// to do}}
}

return to previous screen

In SwiftUI, if we need to return to previous screen, we just could use:

    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>...presentationMode.wrappedValue.dismiss()

.presentationMode is a SwiftUI Enviromental Variable, we have this one on tap and we can refer it via @Enviroment(.presentationMod).

Then we can call presentationMode.wrappedValue.dismiss() to return to parent screen /dismiss current screen.

Please add these statements into SecondView secondView and ThirdView.

Popup modal view

Sometimes, we want to present a full screen view without adding it to navigation stack. In this case, we could use some special view modifiers other than NavigaionLink.

But we still need a bool variable to indicate the showing status of upcoming view. Add a bool into ThirdView:

@State var forthViewShowing = false

Then we can popup another view. In ThirdView.swift, we use fullScreenCover modifier to show the ForthView :

var body: some View {VStack{...}.fullScreenCover(isPresented: $navigation.forthViewShowing) {ForthView()}
}

In VStack closure, we have this button to pop the new view:

            Button("Pop up a modal view"){navigation.forthViewShowing = true}

The only thing we need to do is set forthViewShowing to true when users tap this Button and this make the ForthView pop up from bottom of screen instantaneously.

Here is what ForthView look like:

    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>@State var forthViewShowing = falsevar body: some View {VStack {Text("4th view")Button("close", action: {presentationMode.wrappedValue.dismiss()})}}

We add a close Button. When users tap this Button we directly close the ForthView. As you see, presentationMode.wrappedValue.dismiss() can act on not only navigation-styled screen but also pop-styled screen.

Back to root view

SwiftUI’s Navigation system can back to not only preceding view but also root view. For example, let’s back to root view from ThirdView. We add a new Button in ThirdView.swift, just below the ‘Pop up a modal view’ button:

 	Button("Pop to root view") {navigation.secondViewShowing = false}

Build & run, you can see there is a new button says ‘Pop to root view’:

Click it, we will directly back to FirstView skipping the SecondView.

All we’ve done is set secondViewShowing value to false.It’s a magic!

Only use NavigationView in root view

I’d like to grab your attention! Don’t use NabigationView more than once, becuase it cause embedded more than one NavigationBar as below:

Even if you continue pushing new views into navigation stack, you can get more stacked NavigationBars:

So please check your SwiftUI views carefully in case you used NavigationViews outside of root view:

这篇关于SwiftUI: Navigation all know的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/356692

相关文章

Web Navigation POJ 1028 栈操作

模拟平时上网打开网页的操作,值得注意的是,如果BACK回一个网页之后进行VISIT操作,之前的网页FORWARD都回不去了 #include<cstdio>#include<cstring>#include<iostream>#include<stack>using namespace std;#define MAXD 20#define MAX_SIZE 100 + 10co

HTB-You know 0xDiablos

引言 项目概述:HTB的EASY难度 PWN靶机 You know 0xDiablos https://app.hackthebox.com/challenges/106 本文详细解释了溢出payload的结构以及为什么要这样构造,友好的为想要入手PWN的朋友解释了原理技术点涉及: 32位linux逆向、32位程序调用、栈溢出目标与读者:网络安全兴趣爱好者、PWN新手 基本情况 运行看看

Avoided redundant navigation to current location: 路由相同报错

vue-router有一个内置保护机制,它会阻止不必要的重复导航,以提高性能并避免不必要的计算。 具体来说,错误信息中的就是试图访问的路径时,应用程序已经在当前这个路径上。因此,vue-router检测到了这个重复的导航请求,就发出了警告。 通常情况下,这种警告并不需要特别处理,因为这只是一个优化措施,防止不必要的导航。但是如果你频繁遇到这种情况,可能需要检查触发导航的部分代码逻辑是否有必要进

Navigation运维网站导航工具

Navigation 导航网址 recommend: China-Gitee,Other-Github 功能 新增分类删除分类编辑分类查询分类新增网址删除网址编辑网址查询网址新建管理员账号、菜单权限编辑管理员账号、菜单权限导出xls 【管理员】 【访客】 开发环境 名称版本nodejs8.11.4vue2.X 安装 简单命令行即可使用 Doc

行业Know How在AI提示词中的重要性

现在AI这玩意儿可是火得不得了,ChatGPT、Claude这些大语言模型简直是让人眼花缭乱。可是呢,你要是不会用,那就跟给猴子一部iPhone有啥区别? 咱得明白,这AI不是神仙,它也是需要"调教"的。 你给它的提示词就像是给小孩子布置作业,说得不清不楚,它可能就给你整出些稀奇古怪的东西来。所以啊,行业know-how就像是一个经验丰富的家长,知道怎么跟孩子沟通,怎么布置作业才能让孩子

HarmonyOS鸿蒙开发( Beta5版)Navigation组件常规加载与动态加载

简介 应用在加载页面时,如果引入暂时不需要加载的模块,会导致页面加载缓慢和不必要的内存占用。例如当页面使用Navigation组件时,主页默认加载子页面,此时若子页面使用了Web组件,则会提前加载Web相关的so库,即使并没有进入子页面。 本文推荐使用动态加载解决上述问题,不在进入主页面时就将所有模块都加载进来,而是按需加载模块,增加应用灵活性,提升应用性能。 场景示例 下面示例应用

Gazebo Harmonic gz-harmonic 和 ROS2 Jazzy 思考题 建图和导航 SLAM Navigation

仿真 效果还挺好的。  SLAM建图 导航 …… 提示 这篇文档详细介绍了如何在ROS 2环境中使用SLAM(Simultaneous Localization and Mapping,即同时定位与地图构建)和Nav2(Navigation 2,ROS 2的导航框架)来让机器人一边构建环境地图一边进行导航。以下是对该文档的详细总结: 概述 文档主要面向ROS 2用户

【Android】Navigation动态设置Graph和Launch参数

需求 Activity和Fragment可以服用,不同的启动方式,Fragment调用栈不同 方案 不同的启动方式,通过代码动态设置,使用不同的NavGraph 注意 动态设置代码是在onCreate之后执行的 NavHost如果指定了Graph,在onCreate后会立刻启动,可能会造成启动两个Fragment栈 所以使用动态Graph方案的话,在NavHost中不能设置app:n

SwiftUI 如何恣意定制和管理系统中的窗口(Window)

概览 在苹果大屏设备上,我们往往需要借助多窗口(Multiwindow)来充分利用海量的显示空间,比如 Mac,iPad 以及 AppleTV 系统 等等。 所幸的是,SwiftUI 对多窗口管理提供了很好的支持。利用 SwiftUI 我们可以非常轻松的设置窗口在屏幕上的位置,大小以及拖动反馈。 在本篇博文中,您将学到如下内容: 概览1. 限制窗口大小2. 任性选择窗口放置位置3

【TabBar嵌套Navigation案例-推送和提醒界面的内容 Objective-C语言】

一、使用兑换码这个页面,看一下示例程序 1.当我点击这个使用兑换码Cell的时候,我要跳到另一个页面, 我要跳到一个控制器,这是一个普通的控制器,然后呢,这个页面啊,怎么着来做,首先,我要点击这个Cell,做一些事情,那我肯定要有它的点击事件, 这是第一,然后呢,接下来啊,我先来写,一会儿我们再来去说,我们先来用普通的正常的方式,大家都熟悉的方式,先来写,首先,在这个里边,我们说