★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/)
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/11075287.html
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
在标记应用程序中,用户可以标记他们喜欢的地方,并过滤列表以显示他们的最爱。要创建此功能,您首先要在列表中添加一个开关,以便用户只关注他们的收藏夹,然后您将添加一个星形按钮,用户点击该按钮以标记为收藏夹。
下载入门项目并按照本教程进行操作,或打开已完成的项目并自行探索代码。
一、标记用户最喜欢的地标
首先,通过增强列表,一目了然地向用户展示他们的最爱。为每个显示最喜欢的地标的人添加一颗星。LandmarkRow
第1步
打开起始点Xcode项目,然后在Project导航器中选择。LandmarkRow.swift
第2步
在间隔符之后,在if
语句中添加星形图像以测试当前地标是否是最喜欢的。
在SwiftUI块中,您使用if
语句来有条件地包含视图。
1 import SwiftUI 2 3 struct LandmarkRow: View { 4 var landmark: Landmark 5 6 var body: some View { 7 HStack { 8 landmark.image(forSize: 50) 9 Text(landmark.name) 10 Spacer() 11 12 if landmark.isFavorite { 13 Image(systemName: "star.fill") 14 .imageScale(.medium) 15 } 16 } 17 } 18 } 19 20 struct LandmarkRow_Previews: PreviewProvider { 21 static var previews: some View { 22 Group { 23 LandmarkRow(landmark: landmarkData[0]) 24 LandmarkRow(landmark: landmarkData[1]) 25 } 26 .previewLayout(.fixed( 300, height: 70)) 27 } 28 }
第3步
由于系统图像是基于矢量的,因此您可以使用修改器更改其颜色。foregroundColor(_:)
每当地标的财产出现时,这颗星就会出现。您将在本教程后面看到如何修改该属性。isFavorite
true
1 import SwiftUI 2 3 struct LandmarkRow: View { 4 var landmark: Landmark 5 6 var body: some View { 7 HStack { 8 landmark.image(forSize: 50) 9 Text(landmark.name) 10 Spacer() 11 12 if landmark.isFavorite { 13 Image(systemName: "star.fill") 14 .imageScale(.medium) 15 .foregroundColor(.yellow) 16 } 17 } 18 } 19 } 20 21 struct LandmarkRow_Previews: PreviewProvider { 22 static var previews: some View { 23 Group { 24 LandmarkRow(landmark: landmarkData[0]) 25 LandmarkRow(landmark: landmarkData[1]) 26 } 27 .previewLayout(.fixed( 300, height: 70)) 28 } 29 }
二、过滤列表视图
您可以自定义列表视图,以便显示所有地标,或仅显示用户的收藏夹。为此,您需要在该类型中添加一些状态。LandmarkList
状态是一个值或一组值,它们可以随时间变化,并且会影响视图的行为,内容或布局。您使用具有该@State
属性的属性将状态添加到视图。
第1步
在项目导航器中选择。添加一个名为to 的属性,其初始值设置为:
LandmarkList.swift
@State
showFavoritesOnly
LandmarkList
false
1 import SwiftUI 2 3 struct LandmarkList: View { 4 @State var showFavoritesOnly = false 5 6 var body: some View { 7 NavigationView { 8 List(landmarkData) { landmark in 9 NavigationButton(destination: LandmarkDetail(landmark: landmark)) { 10 LandmarkRow(landmark: landmark) 11 } 12 } 13 .navigationBarTitle(Text("Landmarks")) 14 } 15 } 16 } 17 18 struct LandmarkList_Previews: PreviewProvider { 19 static var previews: some View { 20 LandmarkList() 21 } 22 }
第2步
单击“ 继续”按钮刷新画布。
当您对视图的结构进行更改(如添加或修改属性)时,需要手动刷新画布。
第3步
通过检查属性和每个值来过滤地标列表。showFavoritesOnly
landmark.isFavorite
1 import SwiftUI 2 3 struct LandmarkList: View { 4 @State var showFavoritesOnly = false 5 6 var body: some View { 7 NavigationView { 8 List(landmarkData) { landmark in 9 if !self.showFavoritesOnly || landmark.isFavorite { 10 NavigationButton(destination: LandmarkDetail(landmark: landmark)) { 11 LandmarkRow(landmark: landmark) 12 } 13 } 14 } 15 .navigationBarTitle(Text("Landmarks")) 16 } 17 } 18 } 19 20 struct LandmarkList_Previews: PreviewProvider { 21 static var previews: some View { 22 LandmarkList() 23 } 24 }
三、添加控件以切换状态
要让用户控制列表的过滤器,您需要添加一个可以改变其值的控件。您可以通过将绑定传递给切换控件来完成此操作。showFavoritesOnly
结合充当到可变状态的参考。当用户将切换从关闭切换为打开然后再关闭时,控件使用绑定相应地更新视图的状态。
创建嵌套组以将标记转换为行。ForEach
要在列表中组合静态和动态视图,或组合两个或多个不同的动态视图组,请使用该类型而不是将数据集合传递给。ForEach
List
1 import SwiftUI 2 3 struct LandmarkList: View { 4 @State var showFavoritesOnly = true 5 6 var body: some View { 7 NavigationView { 8 List { 9 ForEach(landmarkData) { landmark in 10 if !self.showFavoritesOnly || landmark.isFavorite { 11 NavigationButton(destination: LandmarkDetail(landmark: landmark)) { 12 LandmarkRow(landmark: landmark) 13 } 14 } 15 } 16 } 17 .navigationBarTitle(Text("Landmarks")) 18 } 19 } 20 } 21 22 struct LandmarkList_Previews: PreviewProvider { 23 static var previews: some View { 24 LandmarkList() 25 } 26 }
第2步
添加Toggle
视图作为视图的第一个子项List
,将绑定传递给。showFavoritesOnly
您可以使用$
前缀来访问对状态变量或其某个属性的绑定。
1 import SwiftUI 2 3 struct LandmarkList: View { 4 @State var showFavoritesOnly = true 5 6 var body: some View { 7 NavigationView { 8 List { 9 Toggle(isOn: $showFavoritesOnly) { 10 Text("Favorites only") 11 } 12 13 ForEach(landmarkData) { landmark in 14 if !self.showFavoritesOnly || landmark.isFavorite { 15 NavigationButton(destination: LandmarkDetail(landmark: landmark)) { 16 LandmarkRow(landmark: landmark) 17 } 18 } 19 } 20 } 21 .navigationBarTitle(Text("Landmarks")) 22 } 23 } 24 } 25 26 struct LandmarkList_Previews: PreviewProvider { 27 static var previews: some View { 28 LandmarkList() 29 } 30 }
第3步
使用实时预览并通过点击切换来尝试此新功能。
四、使用可绑定对象进行存储
要准备用户控制哪些特定地标是收藏夹,您首先将地标数据存储在可绑定对象中。
可绑定对象是数据的自定义对象,可以从SwiftUI环境中的存储绑定到视图。SwiftUI监视可能影响视图的可绑定对象的任何更改,并在更改后显示正确的视图版本。
![](https://img2018.cnblogs.com/blog/960222/201906/960222-20190627172319501-881250218.png)
第1步
创建一个名为的新Swift文件并声明一个模型类型。UserData.swift
1 import SwiftUI 2 3 final class UserData: BindableObject { 4 5 }
第2步
使用作为发布者添加所需的属性。didChange
PassthroughSubject
PassthroughSubject
是一个来自Combine框架的简单发布者,它立即将任何值传递给其订阅者。SwiftUI通过此发布者订阅您的对象,并更新数据更改时需要刷新的所有视图。
1 import SwiftUI 2 import Combine 3 4 final class UserData: BindableObject { 5 let didChange = PassthroughSubject<UserData, Never>() 6 }
第3步
为和添加存储的属性及其初始值。showFavoritesOnly
landmarks
每当客户端更新模型的数据时,可绑定对象都需要通知其订户。当任一属性更改时,应通过其发布者发布更改。UserData
didChange
1 import SwiftUI 2 import Combine 3 4 final class UserData: BindableObject { 5 let didChange = PassthroughSubject<UserData, Never>() 6 7 var showFavoritesOnly = false 8 var landmarks = landmarkData 9 }
第4步
为通过发布者发送更新的两个属性创建处理程序。didSet
didChange
1 import SwiftUI 2 import Combine 3 4 final class UserData: BindableObject { 5 let didChange = PassthroughSubject<UserData, Never>() 6 7 var showFavoritesOnly = false { 8 didSet { 9 didChange.send(self) 10 } 11 } 12 13 var landmarks = landmarkData { 14 didSet { 15 didChange.send(self) 16 } 17 } 18 }
五、在视图中采用模型对象
UserData
![](https://img2018.cnblogs.com/blog/960222/201906/960222-20190627193402433-101868361.png)
第1步
在用属性替换声明,并向预览添加修改器。
LandmarkList.swift
showFavoritesOnly
@EnvironmentObject
environmentObject(_:)
只要修改器已应用于父级,此属性就会自动获取其值。userData
environmentObject(_:)
1 import SwiftUI 2 3 struct LandmarkList: View { 4 @EnvironmentObject var userData: UserData 5 6 var body: some View { 7 NavigationView { 8 List { 9 Toggle(isOn: $showFavoritesOnly) { 10 Text("Favorites only") 11 } 12 13 ForEach(landmarkData) { landmark in 14 if !self.showFavoritesOnly || landmark.isFavorite { 15 NavigationButton(destination: LandmarkDetail(landmark: landmark)) { 16 LandmarkRow(landmark: landmark) 17 } 18 } 19 } 20 } 21 .navigationBarTitle(Text("Landmarks")) 22 } 23 } 24 } 25 26 struct LandmarkList_Previews: PreviewProvider { 27 static var previews: some View { 28 LandmarkList() 29 .environmentObject(UserData()) 30 } 31 }
第2步
通过访问相同的属性来替换使用。showFavoritesOnly
userData
就像在@State
属性上一样,您可以使用前缀访问对象成员的绑定。userData
$
1 import SwiftUI 2 3 struct LandmarkList: View { 4 @EnvironmentObject var userData: UserData 5 6 var body: some View { 7 NavigationView { 8 List { 9 Toggle(isOn: $userData.showFavoritesOnly) { 10 Text("Favorites only") 11 } 12 13 ForEach(landmarkData) { landmark in 14 if !self.userData.showFavoritesOnly || landmark.isFavorite { 15 NavigationButton(destination: LandmarkDetail(landmark: landmark)) { 16 LandmarkRow(landmark: landmark) 17 } 18 } 19 } 20 } 21 .navigationBarTitle(Text("Landmarks")) 22 } 23 } 24 } 25 26 struct LandmarkList_Previews: PreviewProvider { 27 static var previews: some View { 28 LandmarkList() 29 .environmentObject(UserData()) 30 } 31 }
第3步
使用创建时的数据实例。userData.landmarks
ForEach
1 import SwiftUI 2 3 struct LandmarkList: View { 4 @EnvironmentObject var userData: UserData 5 6 var body: some View { 7 NavigationView { 8 List { 9 Toggle(isOn: $userData.showFavoritesOnly) { 10 Text("Favorites only") 11 } 12 13 ForEach(userData.landmarks) { landmark in 14 if !self.userData.showFavoritesOnly || landmark.isFavorite { 15 NavigationButton(destination: LandmarkDetail(landmark: landmark)) { 16 LandmarkRow(landmark: landmark) 17 } 18 } 19 } 20 } 21 .navigationBarTitle(Text("Landmarks")) 22 } 23 } 24 } 25 26 struct LandmarkList_Previews: PreviewProvider { 27 static var previews: some View { 28 LandmarkList() 29 .environmentObject(UserData()) 30 } 31 }
第4步
将修改器添加到。SceneDelegate.swift
environmentObject(_:)
LandmarkList
如果您在模拟器或设备上构建和运行标记,而不是使用预览,
则此更新可确保在环境中具有对象LandmarkList
UserData
1 import UIKit 2 import SwiftUI 3 4 class SceneDelegate: UIResponder, UIWindowSceneDelegate { 5 var window: UIWindow? 6 7 func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 8 // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 9 // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 10 // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 11 12 // Use a UIHostingController as window root view controller 13 let window = UIWindow(frame: UIScreen.main.bounds) 14 window.rootViewController = UIHostingController( 15 rootView: LandmarkList() 16 .environmentObject(UserData()) 17 ) 18 self.window = window 19 window.makeKeyAndVisible() 20 } 21 22 // ... 23 }
第5步
更新视图以使用环境中的对象。LandmarkDetail
UserData
您将在访问或更新地标的最爱状态时使用,以便您始终访问该数据的正确版本。landmarkIndex
1 import SwiftUI 2 3 struct LandmarkDetail: View { 4 @EnvironmentObject var userData: UserData 5 var landmark: Landmark 6 7 var landmarkIndex: Int { 8 userData.landmarks.firstIndex(where: { $0.id == landmark.id })! 9 } 10 11 var body: some View { 12 VStack { 13 MapView(landmark: landmark) 14 .frame(height: 300) 15 16 CircleImage(image: landmark.image(forSize: 250)) 17 .offset(y: -130) 18 .padding(.bottom, -130) 19 20 VStack(alignment: .leading) { 21 Text(landmark.name) 22 .font(.title) 23 HStack(alignment: .top) { 24 Text(landmark.park) 25 .font(caption) 26 Spacer() 27 Text(landmark.state) 28 .font(.caption) 29 } 30 } 31 .padding() 32 33 Spacer() 34 } 35 .navigationBarTitle(Text(landmark.name), displayMode: .inline) 36 } 37 } 38 39 struct LandmarkDetail_Preview: PreviewProvider { 40 static var previews: some View { 41 LandmarkDetail(landmark: landmarkData[0]) 42 .environmentObject(UserData()) 43 } 44 }
第6步
切换回并打开实时预览以验证一切正常。LandmarkList.swift
六、为每个地标创建收藏按钮
![](https://img2018.cnblogs.com/blog/960222/201906/960222-20190627194015686-714024393.png)
第1步
将地标的名称嵌入到。LandmarkDetail.swift
HStack
1 import SwiftUI 2 3 struct LandmarkDetail: View { 4 @EnvironmentObject var userData: UserData 5 var landmark: Landmark 6 7 var landmarkIndex: Int { 8 userData.landmarks.firstIndex(where: { $0.id == landmark.id })! 9 } 10 11 var body: some View { 12 VStack { 13 MapView(landmark: landmark) 14 .frame(height: 300) 15 16 CircleImage(image: landmark.image(forSize: 250)) 17 .offset(y: -130) 18 .padding(.bottom, -130) 19 20 VStack(alignment: .leading) { 21 HStack { 22 Text(landmark.name) 23 .font(.title) 24 } 25 26 HStack(alignment: .top) { 27 Text(landmark.park) 28 .font(caption) 29 Spacer() 30 Text(landmark.state) 31 .font(.caption) 32 } 33 } 34 .padding() 35 36 Spacer() 37 } 38 .navigationBarTitle(Text(landmark.name), displayMode: .inline) 39 } 40 } 41 42 struct LandmarkDetail_Preview: PreviewProvider { 43 static var previews: some View { 44 LandmarkDetail(landmark: landmarkData[0]) 45 .environmentObject(UserData()) 46 } 47 }
第2步
在地标名称旁边创建一个新按钮。使用if
- else
条件语句提供不同的图像,指示地标是否是最喜欢的。
在按钮的action
关闭,该代码使用与对象更新到位的标志性建筑。landmarkIndex
userData
1 import SwiftUI 2 3 struct LandmarkDetail: View { 4 @EnvironmentObject var userData: UserData 5 var landmark: Landmark 6 7 var landmarkIndex: Int { 8 userData.landmarks.firstIndex(where: { $0.id == landmark.id })! 9 } 10 11 var body: some View { 12 VStack { 13 MapView(landmark: landmark) 14 .frame(height: 300) 15 16 CircleImage(image: landmark.image(forSize: 250)) 17 .offset(y: -130) 18 .padding(.bottom, -130) 19 20 VStack(alignment: .leading) { 21 HStack { 22 Text(landmark.name) 23 .font(.title) 24 25 Button(action: { 26 self.userData.landmarks[self.landmarkIndex].isFavorite.toggle() 27 }) { 28 if self.userData.landmarks[self.landmarkIndex].isFavorite { 29 Image(systemName: "star.fill") 30 .foregroundColor(Color.yellow) 31 } else { 32 Image(systemName: "star") 33 .foregroundColor(Color.gray) 34 } 35 } 36 } 37 38 HStack(alignment: .top) { 39 Text(landmark.park) 40 .font(caption) 41 Spacer() 42 Text(landmark.state) 43 .font(.caption) 44 } 45 } 46 .padding() 47 48 Spacer() 49 } 50 .navigationBarTitle(Text(landmark.name), displayMode: .inline) 51 } 52 } 53 54 struct LandmarkDetail_Preview: PreviewProvider { 55 static var previews: some View { 56 LandmarkDetail(landmark: landmarkData[0]) 57 .environmentObject(UserData()) 58 } 59 }
第3步
切换回,并打开实时预览。LandmarkList.swift
当您从列表导航到详细信息并点击按钮时,返回列表时这些更改仍然存在。由于两个视图都在环境中访问相同的模型对象,因此这两个视图保持一致性。