前提回顾

不知不觉现已到了第七章了,在过往每一个章节中,咱们都添加和完结项目的一些功用,让Linkwirld这款产品越来越挨近完结品。

一款以用户操作为核心功用的产品,需求包含增修正查4部分功用,在之前的章节中咱们现已完结了查看、新增、删除的功用,还缺少关于身份卡进行修正的功用。修正操作为用户关于之前创立的内容的调整或者更新,在ToDo、Note等应用当中很是常见。

那么在本章中,咱们来完结后修正相关的操作。

功用分析:新增和修正页面的差异

许多时候都会有一个疑问,现在页面和修正页面到底有没有差异?开发人员究竟只需求保护一个页面仍是要保护两个页面?

一般情况下,因为页面款式的相似性,新增和修正操作在开发的角度上是需求做到共用的。但新增和修正页面除了数据绑定外没有太大的款式区分时,咱们主张能够将款式或者组件抽离出来,然后再独自应用。

视图复用:创立独自的构件

在新增和修正页面中,咱们会发现有几部分视图内容是能够复用的:titleInputView渠道输入框、platformPicker渠道挑选器、indexURLView链接地址。如下图所示:

因而关于这三部分,咱们能够将其抽离出来建立独自的构件,如此便能够在新增页面和修正页面都能够引证。而且假如需求修正款式,也只需求修正构件的款式,新增和修正页面的款式就能够一致改变。

咱们创立一个新的文件夹,命名为Artifacts,而且创立3个SwiftUI文件,并为其命名为TitleInputView、PlatformPicker、IndexURLView,如下图所示:

咱们先来到TitleInputView文件,将原来NewView中的titleInputView视图代码仿制过来,如下代码所示:

import SwiftUI
struct TitleInputView: View {
    @Binding var title: String
    var body: some View {
        TextField("请输入头衔", text: $title)
            .padding()
            .background(Color(.systemGray6))
            .cornerRadius(8)
            .padding(.horizontal)
            .disableAutocorrection(true)
            .autocapitalization(.none)
    }
}
struct TitleInputView_Previews: PreviewProvider {
    static var previews: some View {
        TitleInputView(title: .constant(""))
    }
}

上述代码中,除了TextField输入框相关代码仿制过来,咱们还需求完善相关的绑定参数。声明一个用于双向绑定输入框的参数title,而且在TitleInputView预览时给参数赋予默认值。

紧接着来到PlatformPicker视图,将原来NewView中的platformPicker视图代码仿制过来,如下代码所示:

import SwiftUI
struct PlatformPicker: View {
    @Binding var platformIcon: String
    @Binding var platformName: String
    let platforms = [
        ("稀土技能社区", "icon_juejin"),
        ("CSDN博客", "icon_csdn"),
        ("阿里云社区", "icon_aliyun"),
        ("华为云社区", "icon_huaweiyun"),
    ]
    var gridItemLayout = [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]
    var body: some View {
        ScrollView {
            LazyVGrid(columns: gridItemLayout, spacing: 10) {
                ForEach(0 ..< platforms.count, id: .self) { item in
                    if platforms[item].0 == platformName {
                        Image(platforms[item].1)
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                            .frame(width: 48, height: 48)
                            .clipShape(Circle())
                            .overlay(
                                Circle()
                                    .stroke(Color.green, lineWidth: 4)
                            )
                    } else {
                        Image(platforms[item].1)
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                            .frame(width: 48, height: 48)
                            .clipShape(Circle())
                            .onTapGesture {
                                platformIcon = platforms[item].1
                                platformName = platforms[item].0
                            }
                    }
                }
            }
        }
        .padding()
        .background(Color(.systemGray6))
        .cornerRadius(8)
        .padding(.horizontal)
        .frame(maxHeight: 140)
    }
}
struct PlatformPicker_Previews: PreviewProvider {
    static var previews: some View {
        PlatformPicker(platformIcon: .constant("icon_juejin"), platformName: .constant("稀土技能社区"))
    }
}

上述代码中,咱们仍旧讲需求声明双向绑定的变量,如:platformIcon渠道图标、platformName渠道称号,值得注意的是,原有咱们声明的selectedItem完全能够换成判别platformName是否等于点击的称号,减少一个参数。如下代码所示:

platforms[item].0 == platformName

声明双向绑定变量还需求在预览PlatformPicker时添加参数的默认值,如下代码所示:

PlatformPicker(platformIcon: .constant("icon_juejin"), platformName: .constant("稀土技能社区"))

下一步到IndexURLView视图,将原来NewView中的indexURLView视图代码仿制过来,如下代码所示:

import SwiftUI
struct IndexURLView: View {
    @Binding var indexURL:String
    var body: some View {
        ZStack(alignment: .topLeading) {
            TextEditor(text: $indexURL)
                .font(.system(size: 17))
                .padding(15)
                .disableAutocorrection(true)
                .autocapitalization(.none)
            if indexURL.isEmpty {
                Text("请输入主页链接")
                    .foregroundColor(Color(UIColor.placeholderText))
                    .padding(20)
            }
        }
        .background(Color(.systemGray6))
        .cornerRadius(8)
        .padding()
        .frame(maxHeight: 240)
    }
}
struct IndexURLView_Previews: PreviewProvider {
    static var previews: some View {
        IndexURLView(indexURL: .constant(""))
    }
}

完结之后,咱们就能够回到NewView视图中,将原来的参数以及titleInputView渠道输入框视图、platformPicker渠道挑选器视图、indexURLView链接地址视图的代码删掉,如下图所示:

删除代码后,咱们运用独自建立的构件来重新建立款式,如下代码所示:

TitleInputView(title: $title)
PlatformPicker(platformIcon: $platformIcon, platformName: $platformName)
IndexURLView(indexURL: $indexURL)

如此,NewView视图在坚持原有功用款式不变的情况下,代码量也精简许多。

界面规划:创立EditView修正页面

完结独自的构件后,咱们就能够来完结修正页面的规划。创立一个新的SwiftUI文件,命名为EditView,如下图所示:

咱们先建立根底的款式,示例:顶部导航菜单、页面标题、封闭按钮等等,如下图所示:

import SwiftUI
struct EditView: View {
    @Environment(.presentationMode) var presentationMode
    var body: some View {
        NavigationStack {
            Text("Hello, World!")
                .navigationBarTitle("修正身份卡", displayMode: .inline)
                .navigationBarItems(trailing: closeBtn())
        }
    }
    // 封闭按钮
    func closeBtn() -> some View {
        Button(action: {
            self.presentationMode.wrappedValue.dismiss()
        }) {
            Image(systemName: "xmark.circle.fill")
                .font(.system(size: 17))
                .foregroundColor(.gray)
        }
    }
}

上述代码中,咱们根本和NewView页面的规划一样,运用NavigationStack建立顶部导航菜单,并运用navigationBarTitle修饰符设置导航标题,运用navigationBarItems修饰符设置封闭页面按钮,并通过声明全局变量presentationMode和建立独自的封闭按钮closeBtn来完成封闭修正页面。

关于修正保存按钮,款式和操作和NewView页面可能不一样,咱们也独自构建按钮,如下代码所示:

// 修正更新按钮
func updateBtn() -> some View {
    Button(action: {
        self.presentationMode.wrappedValue.dismiss()
    }) {
        Text("确认更新")
            .font(.system(size: 17))
            .foregroundColor(.white)
            .bold()
            .padding()
            .frame(maxWidth: .infinity)
            .background(Color.blue)
            .cornerRadius(8)
            .padding(.horizontal)
    }
}

完结后将修正按钮updateBtn添加到EditView修正页面视图中,如下代码所示:

VStack(spacing: 15) {
	updateBtn()
}

在引证构件之前,咱们先来理解下数据联系。

EditView修正页面和ContentView主页的数据联系是,用户在ContentView主页点击单张身份卡片,然后翻开EditView修正页面,并将ContentView主页身份卡片的数据传递到EditView修正页面。

咱们能够声明一个符合Model数据模型的参数,然后无论是TitleInputView、PlatformPicker、IndexURLView的参数都绑定Model数据模型的参数,然后该数据模型双向绑定到ContentView主页中,就能够将ContentView主页的数据传递过来,如下代码所示:

@State var model: Model

然后调用TitleInputView、PlatformPicker、IndexURLView构件,而且绑定model模型的值,如下代码所示:

VStack(spacing: 15) {
    TitleInputView(title: $model.title)
    PlatformPicker(platformIcon: $model.platformIcon, platformName: $model.platformName)
    IndexURLView(indexURL: $model.indexURL)
    updateBtn()
    Spacer()
}

交互动作:翻开EditView修正页面

完结后,咱们回到ContentView主页中,咱们来完成页面跳转的逻辑。首要咱们先声明一个用于页面跳转的参数,如下代码所示:

@State var showEditView: Bool = false

然后在CardView视图中添加页面跳转的办法,这儿也能够运用Sheet修饰符,如下代码所示:

// 翻开修正弹窗
.sheet(isPresented: self.$showEditView, onDismiss: {self.showEditView = false }) {
    EditView(model: self.item ?? Model(platformIcon: "", title: "", platformName: "", indexURL: ""))
}

上述代码中,咱们运用sheet修饰符用户翻开模态弹窗,触发操作绑定声明好的变量showEditView,翻开的模态弹窗的页面为EditView修正页面。

因为EditView修正页面需求传入参数,这儿咱们挑选传入的对应model的参数为声明的item,当item不存在时,则默认传入一个符合Model格局的数据,防止报错。

关于修正操作,咱们能够给CardView添加多一个指示符,奉告用户这个卡片是能够被修正的,如下代码所示:

Image(systemName: "ellipsis")
	.padding()
	.foregroundColor(.black)
	.gesture(
		TapGesture()
		.onEnded({
			self.showEditView.toggle()
		})
	)

上述代码中,咱们添加了一个Image图片到CardView视图中,当点击这个Image图片时,翻开EditView修正页面。

考虑到在ContentView咱们现已运用NavigationLink顶部导航菜单进行跳转,那么假如咱们给按钮添加点击事情,会存在两个点击事情抵触的情况。

咱们先注释掉NavigationLink相关的代码,如下图所示:

完结后,咱们点击刚刚创立的修正按钮,预览下作用,如下图所示:

新增功用:修正并更新内容

完结款式后,咱们来完成下修正更新的逻辑,来到ViewModel视图模型中,咱们创立一个修正并更新的办法,如下图所示:

// 修正更新数据项
func editItem(item: Model) {
    if let id = models.firstIndex(where: { $0.id == item.id }) {
        models[id] = item
    }
}

修正操作很简单,首要要找到点击修正的数据项的ID在整个models数据会集的位置,更新后将更新内容赋予当时的ID,便完成了修正更新操作。

回到EditView视图中,先引进ViewModel视图模型,如下代码所示:

var viewModel: ViewModel

然后在EditView视图预览时,给声明的viewModel赋予默认值,如下代码所示:

EditView(model: Model(platformIcon: "", title: "", platformName: "", indexURL: ""), viewModel: ViewModel())

因为咱们在EditView视图声明了变量viewModel,因而在运用EditView视图的页面也要进行参数绑定。

咱们来到ContentView文件,在CardView视图中,需求弥补viewModel相关的参数,如下图所示:

绑定完结后,咱们再回到EditView视图,在点击updateBtn更新按钮时,调用ViewModel视图模型中的editItem办法,如下代码所示:

self.viewModel.editItem(item: model)

如此,咱们便完成了修正更新的办法。

交互动作:翻开HonePageView页面

完结打卡修正弹窗,并完成修正更新操作后,咱们来弥补完善下翻开HonePageView页面的交互。在之前的章节咱们运用NavigationLink导航菜单跳转方式完成了页面跳转,因为会和咱们点击修正操作相抵触,因而咱们注释了这部分代码。

这儿咱们再介绍一种弹窗,fullScreenCover全屏掩盖弹窗,用fullScreenCover也能够完成页面跳转的作用。

首要先声明翻开弹窗的变量,咱们在CardView视图中声明变量,如下代码所示:

@State var showHomePageView: Bool = false

然后完成调用fullScreenCover全屏掩盖弹窗的办法,如下代码所示:

//翻开身份卡主页
.fullScreenCover(isPresented: $showHomePageView, content: {
    HomePageView(platformName: platformName, indexURL: indexURL)
        .edgesIgnoringSafeArea(.all)
})

上述代码中,咱们给CardView的主要内容添加了fullScreenCover修饰符,用于翻开全屏掩盖弹窗。

弹窗翻开触发动作绑定声明好的参数showHomePageView,目标地址挑选HomePageView,而且绑定相关的参数,咱们期望这个弹窗全屏展现,添加了edgesIgnoringSafeArea疏忽安全区域修饰符。

翻开弹窗的操作,咱们期望点击身份卡片时翻开,但又不能和修正按钮相抵触。

咱们能够将渠道图标、渠道称号、渠道称号部分的款式再运用一个容器包裹起来,点击这个容器翻开HomePageView页面,这样就能够做到不和修正按钮相抵触了。如下图所示:

最后咱们添加点击事情在这个容器中,当点击时翻开fullScreenCover弹窗,如下代码所示:

.gesture(
	TapGesture()
		.onEnded({
			self.showHomePageView.toggle()
		})
)

因为fullScreenCover弹窗是由下往上翻开,因而咱们能够换一个回来按钮的款式,让操作看起来流畅些,如下图所示:

项目预览

完结后,咱们在模拟器中预览下作用,如下图所示:

5f0fb62c-ad1e-4125-8946-1166e4e6d2e4.gif

项目小结

在本章中,咱们完成了EditView页面的界面规划、页面跳转、修正更新办法,单一个修正操作就很不容易,哭泣。

但最重要的是学习了结构化编程办法,将页面的元素分块,然后抽离创立独自的构件,如此不论在新增页面仍是在修正页面,咱们都只需求保护一套代码,这大大减轻了代码量,也使得编程更加高雅。

那么关于一个本地项目来说,linkworld现已完结了查看、新增、修正、删除操作,似乎能够告一段落了。但是,假如一款产品要成功上线AppStore,那么仅仅在模拟器中运用这些功用是远远不够的,咱们还需求进行本地化存储等功用的开发等等工作。

那么在下一章中,咱们将介绍假如完成本地存储及远端数据存储相关的内容,请坚持等待吧~

版权声明

本文为稀土技能社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!