条件回想

上一章节发布之后,有不少开发的童鞋评论:

本来好好的,经过上一章节的调整后就各个页面开始报错了?

这很正常,刚开始学习SwiftUI的时分,有时分改了一个参数,或许少了一个花括号,愣是找不到哪里写错了。

后边写多了就基本知道哪里需求调整,并且遇到Bug不可怕,就怕的是明明没有报错,并且是跟着项目教程来的,项目运转后便是没作用,也不知道哪里出错了。

找不到自己的薄缺点,这才是最可怕的。

因此不用太过于担心,本章咱们将持续根据完结的ModelViewModel,来完结View相关的内容。

实战编程-主页

单条笔记

咱们回到View文件夹下的ContentView视图,先从NoteListRow单条笔记视图开始调整。

单条笔记涵盖哪些交互逻辑?一个是点击单条笔记的时分翻开修正笔记弹窗,二是点击笔记右侧的“更多”按钮,唤起二次承认弹窗,并可进行操作删去。如下图所示:

实战编程使用SwiftUI从0到1完成一款iOS笔记App(五)

在上一章建立ViewModel功能的时分,咱们提到对单条笔记进行操作需求取得笔记的ID,然后根据单条数据的ID进行操作。那么这儿需求先取得笔记的ID,如下代码所示:

	// 引证viewModel
    @EnvironmentObject var viewModel: ViewModel
    // 取得项目仅有ID
    var itemId: UUID
    // 从模型类中找ID
    var item: NoteModel? {
        return viewModel.getItemById(itemId: itemId)
    }

实战编程使用SwiftUI从0到1完成一款iOS笔记App(五)

上述代码中,咱们先运用@EnvironmentObject全局环境变量引进ViewModel类,并赋值给viewModel。

@EnvironmentObject是一个动态视图特点,为了不管任何时分可绑定目标发生改动时停用当时视图的特点。

紧接着,声明一个变量itemId,遵循UUID格局,作为要运用到的ID。之前咱们运用@ObservedObject取得NoteItem模型类,这儿咱们运用ViewModel就能够弃用本来的这块内容了,直接声明一个变量item,并经过调用viewModel中的getItemById办法取得对应的笔记ID。

在取得笔记ID后,体系可能无法返回相关的数据内容,也便是参数为空的情况导致体系报错,因此咱们运用“?”,当返回的参数值为空的时分,就能够运用默认值填充,防止体系奔溃。

说回正题,由于咱们运用item替换了本来的noteItem,在下面视图对应的参数也需求调整,如下代码所示:

    var body: some View {
        HStack {
            HStack {
                VStack(alignment: .leading, spacing: 10) {
                    Text(item?.writeTime ?? "")
                        .font(.system(size: 14))
                        .foregroundColor(.gray)
                    Text(item?.title ?? "")
                        .font(.system(size: 17))
                        .foregroundColor(.black)
                    Text(item?.content ?? "")
                        .font(.system(size: 14))
                        .foregroundColor(.gray)
                        .lineLimit(1)
                        .multilineTextAlignment(.leading)
                }
            }
            Spacer()
            // 更多操作
            Button(action: {
            }) {
                Image(systemName: "ellipsis")
                    .foregroundColor(.gray)
                    .font(.system(size: 23))
            }
        }
	}

实战编程使用SwiftUI从0到1完成一款iOS笔记App(五)

上述代码中,替换单条笔记绑定的参数,运用item替换noteItem,替换如下:

  • noteItem.writeTime 替换为 item?.writeTime ?? ""
  • noteItem.title 替换为 item?.title ?? ""
  • noteItem.content 替换为 item?.content ?? ""

替换后,咱们来完结两个基本功能,一个是点击笔记的时分,翻开修正笔记弹窗,如下代码所示:

    var body: some View {
        HStack {
            HStack {
                VStack(alignment: .leading, spacing: 10) {
                    Text(item?.writeTime ?? "")
                        .font(.system(size: 14))
                        .foregroundColor(.gray)
                    Text(item?.title ?? "")
                        .font(.system(size: 17))
                        .foregroundColor(.black)
                    Text(item?.content ?? "")
                        .font(.system(size: 14))
                        .foregroundColor(.gray)
                        .lineLimit(1)
                        .multilineTextAlignment(.leading)
                }
            }
            //点击修正
            .onTapGesture {
                self.viewModel.isAdd = false
                self.viewModel.showEditNoteView = true
            }
            Spacer()
            // 更多操作
            Button(action: {
                viewModel.showActionSheet = true
            }) {
                Image(systemName: "ellipsis")
                    .foregroundColor(.gray)
                    .font(.system(size: 23))
            }
        }
	}

实战编程使用SwiftUI从0到1完成一款iOS笔记App(五)

上述代码中,咱们在笔记内容的HStack横向容器中添加了onTapGesture,当点击笔记内容的时分,阐明咱们需求修正笔记,这儿需求更新isAdd是否新增笔记状况为false,然后更新showEditNoteView翻开修正笔记弹窗的触发条件为true。

当用户点击“更多”操作时,更新showActionSheet翻开二次承认弹窗的触发条件为true。

然后咱们完结翻开修正弹窗和翻开删去的二次承认弹窗的操作,如下代码所示:

		// 修正笔记
        .sheet(isPresented: $viewModel.showEditNoteView) {
            //修正笔记弹窗
        }
        // 删去笔记
        .actionSheet(isPresented: self.$viewModel.showActionSheet) {
            ActionSheet(
                title: Text("你确定要删去此项吗?"),
                message: nil,
                buttons: [
                    .destructive(Text("删去"), action: {
                        self.viewModel.deleteItem(itemId: itemId)
                    }),
                    .cancel(Text("撤销")),
                ])
        }

实战编程使用SwiftUI从0到1完成一款iOS笔记App(五)

上述代码中,咱们运用sheet办法,绑定showEditNoteView翻开修正弹窗的触发参数,修正笔记页面后边咱们会和新建笔记页面功用,这儿还没有修正,就先放着。

删去笔记的办法咱们运用actionSheet弹窗,绑定showActionSheet翻开删去二次承认弹窗触发参数,在ActionSheet弹窗内,咱们设置好标题,以及删去按钮的操作,当点击删去的时分,调用viewModel中的deleteItem办法,指定单条笔记的itemId找到对应的笔记进行删去。

完结后,咱们单条笔记部分,除了翻开修正弹窗,其他内容现已修正完结。

笔记列表

回到ContentView视图,咱们修正了单条笔记的内容,因此笔记列表noteListView视图也需求调整,首要引进ViewModel,如下代码所示:

	// 引证viewModel
    @EnvironmentObject var viewModel: ViewModel

实战编程使用SwiftUI从0到1完成一款iOS笔记App(五)

紧接着,咱们换一种办法完结笔记列表,如下代码所示:

	// MARK: 笔记列表
    func noteListView() -> some View {
        List {
            ForEach(viewModel.noteModels) { noteItem in
                NoteListRow(itemId: noteItem.id)
            }
        }
        .listStyle(InsetListStyle())
    }

实战编程使用SwiftUI从0到1完成一款iOS笔记App(五)

上述代码中,咱们换成了运用func办法声明视图,这是另一种创立视图的办法,这样创立视图的好处是,咱们需求声明的参数能够放在ContentView视图中,就不需求在每一个视图中声明。

笔记列表仅有的改动便是NoteListRow单条笔记遍历循环的时分,数组来源于viewModel中的noteModels,然后NoteListRow中的ID为noteItem中的ID

顶部查找栏

再往上是顶部查找栏,如下代码所示:

    // MARK: 查找栏
    func searchBarView() -> some View {
        TextField("查找内容", text: $viewModel.searchText)
            .padding(7)
            .padding(.horizontal, 25)
            .background(Color(.systemGray6))
            .cornerRadius(8)
            .overlay(
                HStack {
                    Image(systemName: "magnifyingglass")
                        .foregroundColor(.gray)
                        .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
                        .padding(.leading, 8)
                    // 修正时显现铲除按钮
                    if viewModel.searchText != "" {
                        Button(action: {
                            self.viewModel.searchText = ""
                            self.viewModel.loadItems()
                        }) {
                            Image(systemName: "multiply.circle.fill")
                                .foregroundColor(.gray)
                                .padding(.trailing, 8)
                        }
                    }
                }
            )
            .padding(.horizontal, 10)
            .onChange(of: viewModel.searchText) { _ in
                if viewModel.searchText != "" {
                    self.viewModel.isSearching = true
                    self.viewModel.searchContet()
                } else {
                    viewModel.searchText = ""
                    self.viewModel.isSearching = false
                    self.viewModel.loadItems()
                }
            }
    }

实战编程使用SwiftUI从0到1完成一款iOS笔记App(五)

查找栏改动的内容有三部分,首要是绑定的输入内容换成了viewModel中的searchText

然后是当查找栏输入时,显现删去的按钮操作,关联的参数也换成viewModel中的searchText,当点击铲除查找内容时,需求将查找栏输入的内容清空,然后再调用loadItems从头加载列表中的数据。

最终是查找栏的查找办法,当输入时,读取searchText输入的内容,假如输入内容不为空,则更新isSearching是否正在查找的状况为true,然后调用searchContet查找办法。假如输入的内容为空,那么更新isSearching是否正在查找的状况为false,并调用loadItems从头加载列表数据。

新建笔记按钮

新建笔记按钮的操作是翻开新建笔记弹窗,修正内容如下代码所示:

	// MARK: 新建笔记按钮
    func newBtnView() -> some View {
        VStack {
            Spacer()
            HStack {
                Spacer()
                Button(action: {
                    self.viewModel.isAdd = true
                    self.viewModel.writeTime = viewModel.getCurrentTime()
                    self.viewModel.title = ""
                    self.viewModel.content = ""
                    self.viewModel.showNewNoteView = true
                }) {
                    Image(systemName: "plus.circle.fill")
                        .font(.system(size: 48))
                        .foregroundColor(.blue)
                }
            }
        }
        .padding(.bottom, 32)
        .padding(.trailing, 32)
    }

实战编程使用SwiftUI从0到1完成一款iOS笔记App(五)

新建笔记按钮修正点就只有点击时的交互动作,当点击新建笔记按钮时,需求更新viewModel中的是否新增状况isAddtrue,标明点击这个按钮是新增,而咱们在单条笔记列表设置isAdd为false,表示当时是在修正笔记。

当新增笔记的时分,调用getCurrentTime设置新建笔记的时刻为当时时刻,设置title标题、content内容为空,并且更新showNewNoteViewtrue,作为翻开新建笔记弹窗的触发参数。

主视图

缺省图视图基本就不用改了,最终回到body部分,修正如下代码所示:

var body: some View {
        NavigationView {
            ZStack {
                if viewModel.isSearching == false && viewModel.noteModels.count == 0 {
                    noDataView()
                } else {
                    VStack {
                        searchBarView()
                        noteListView()
                    }
                }
                newBtnView()
            }
            .navigationBarTitle("想法笔记", displayMode: .inline)
        }
        .sheet(isPresented: $viewModel.showNewNoteView) {
            //翻开新建笔记弹窗
        }
    }

实战编程使用SwiftUI从0到1完成一款iOS笔记App(五)

上述代码中,咱们经过判别isSearching当时是否处于查找状况,以及noteModels数组是是否有数据,来判别当时应该展现缺省视图noDataView,仍是展现searchBarView查找栏和noteListView笔记列表。

现已在主页添加sheet绑定showNewNoteView触发翻开新建笔记弹窗。

最终,咱们还需求在视图预览的时分引证viewModel,如下代码所示:

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environmentObject(ViewModel())
    }
}

好了,主页基本修正完毕了,是不是有点疲劳?回看下整个ContentView的代码,是不是简练了许多,基本没有声明什么参数,因为需求用到的参数都放在了ViewModel里,并且用到的完结功能的办法也都放在ViewModel。

Model用来界说数据模型,View视图用来完结根底交互和页面款式,然后ViewModel用来做数据处理和功能完结,这便是MVVM开发模式

休息好了,咱们就持续吧~

实战编程-新建笔记页

标题&内容输入

首要仍是需求引证ViewModel,才能运用里边声明好的参数。如下代码所示:

	// 引证viewModel
    @EnvironmentObject var viewModel: ViewModel

引证viewModel后,其他声明的参数都能够删掉了。当咱们在主页笔记列表点击单条笔记时,会翻开新建笔记弹窗,并把内容传递过来,因此咱们需求声明模型类参数,如下代码所示:

	// 引证viewModel
    @EnvironmentObject var viewModel: ViewModel
    // 声明参数
    @State var noteModel: NoteModel
    // 封闭弹窗
    @Environment(.presentationMode) var presentationMode

实战编程使用SwiftUI从0到1完成一款iOS笔记App(五)

声明好需求的参数后,咱们来到标题输入框和内容输入框的部分,这是需求绑定的参数就不是之前声明的参数了。当咱们是新建笔记的时分,标题和内容应该是为空,也便是绑定在viewModel声明的titlecontent,而假如是修正笔记,则咱们需求绑定的是来自于点击的单条笔记的内容。如下代码所示:

    // MARK: 标题输入框
    func titleView() -> some View {
        TextField("请输入标题", text: viewModel.isAdd ? $viewModel.title : $noteModel.title)
            .padding()
    }
    // MARK: 内容输入框
    func contentView() -> some View {
        ZStack(alignment: .topLeading) {
            TextEditor(text: viewModel.isAdd ? $viewModel.content : $noteModel.content)
                .font(.system(size: 17))
                .padding()
            if viewModel.isAdd ? (viewModel.content.isEmpty) : (noteModel.content.isEmpty) {
                Text("请输入内容")
                    .foregroundColor(Color(UIColor.placeholderText))
                    .padding(20)
            }
        }
    }

实战编程使用SwiftUI从0到1完成一款iOS笔记App(五)

完结按钮

完结按钮这块,回想下咱们前几章学习的内容,它应该包括几块内容:

一是判别条件,当咱们标题或许内容输入为空的时分,应该提示输入。

二是点击完结操作的时分,也需求判别当时是新增操作仍是修正操作,假如是新增操作,则插入一条新笔记,假如是修正操作,则需求更新笔记的内容。

咱们修正代码如下所示:

    // MARK: 完结按钮
    func saveBtnView() -> some View {
        Button(action: {
            //判别当时是新增仍是修正
            if viewModel.isAdd {
                //判别标题是否为空
                if viewModel.isTextEmpty(text: viewModel.title) {
                    viewModel.showToast = true
                    viewModel.showToastMessage = "请输入标题"
                }
                //判别内容是否为空
                else if viewModel.isTextEmpty(text: viewModel.content) {
                    viewModel.showToast = true
                    viewModel.showToastMessage = "请输入内容"
                }
                //校验经过
                else {
                    // 新增一条笔记
                    self.viewModel.addItem(writeTime: viewModel.getCurrentTime(), title: viewModel.title, content: viewModel.content)
                    //封闭弹窗
                    self.presentationMode.wrappedValue.dismiss()
                }
            } else {
                //判别标题是否为空
                if viewModel.isTextEmpty(text: noteModel.title) {
                    viewModel.showToast = true
                    viewModel.showToastMessage = "标题不能为空"
                }
                //判别内容是否为空
                else if viewModel.isTextEmpty(text: noteModel.content) {
                    viewModel.showToast = true
                    viewModel.showToastMessage = "内容不能为空"
                }
                //校验经过
                else {
                    // 保存一条新笔记
                    self.viewModel.editItem(item: noteModel)
                    //封闭弹窗
                    self.presentationMode.wrappedValue.dismiss()
                }
            }
        }) {
            Text("完结")
                .font(.system(size: 17))
        }
    }

实战编程使用SwiftUI从0到1完成一款iOS笔记App(五)

代码如同许多的样子,其实不然,逻辑很简单。

当点击“完结”按钮时,首要需求isAdd状况判别当时是新增仍是删去。

假如是新增,则判别viewModel中的输入的标题title和内容content是否为空,假如为空,则更改showToast翻开提示信息,现已更新showToastMessage提示信息的内容。假如不为空时,则调用addItem办法新增一条笔记。

假如点击“完结”按钮时的操作为修正操作,则和上面一样的判别,只是判别为空的参数变成了来源于noteModel的标题title和内容content,当为空判别经过后,则调用editItem修正办法更新笔记内容。

最终都是调用presentationMode.wrappedValue.dismiss封闭弹窗,当然直接点击封闭按钮时也能够调用这个办法封闭弹窗。

主视图

最终来到新建笔记的body部分,修正部分就只有标题和toast绑定的参数,如下代码所示:

var body: some View {
        NavigationView {
            VStack {
                Divider()
                titleView()
                Divider()
                contentView()
            }
            .navigationBarTitle(viewModel.isAdd ? "新建笔记" : "修正笔记", displayMode: .inline)
            .navigationBarItems(leading: closeBtnView(), trailing: saveBtnView())
            .toast(present: $viewModel.showToast, message: $viewModel.showToastMessage)
        }
    }

实战编程使用SwiftUI从0到1完成一款iOS笔记App(五)

由于新建笔记页面运用了ViewModel和声明了noteModel模型类,因此咱们假如需求预览该页面,则需求在预览的代码中设置默认值,如下代码所示:

struct NewNoteView_Previews: PreviewProvider {
    static var previews: some View {
        NewNoteView(noteModel: NoteModel(writeTime: "", title: "", content: "")).environmentObject(ViewModel())
    }
}

最终,新建笔记页面修正好后,需求回到ContentView主页,咱们翻开弹窗的路径还没有配置呢。

在新建笔记时,跳转的页面时NewNoteView,如下代码所示:

// 新增笔记
.sheet(isPresented: $viewModel.showNewNoteView) {
	NewNoteView(noteModel: NoteModel(writeTime: "", title: "", content: ""))
}

在修正笔记时,跳转的页面也是NewNoteView,如下代码所示:

// 修正笔记
.sheet(isPresented: $viewModel.showEditNoteView) {
	NewNoteView(noteModel: self.item ?? NoteModel(writeTime: "", title: "", content: ""))
}

项目预览

完结后,运转预览作用如下图所示:

本章小结

祝贺你,完结了运用SwiftUI从0到1完结一款笔记APP的全部内容!

在整个项目过程中,咱们首要学习怎么完结一个个独自的视图,再将一块块的代码组合成一个页面,最终再根底页面和交互的根底上运用Model-View-ViewModel的办法进行开发调整,最终完结整个项目。

回想第一个项目的整个过程,咱们会发现咱们构建视图的办法都是自上而下构建,而完结交互功能、逻辑是自下而上建立。这便是专栏开始之初提到的编程逻辑:

自顶向下逐步求精的模块化规划思想、面向目标的办法自底而上进行开发思想。

编程技巧固然重要,但更重要的是思想办法,办法很简单学会,但观念和习气就没有那么简单改动了。

编程本便是一条没有那么风趣的路,无妨沉下心来,写好每一段代码,写好每一块事务。

看着最终成功运转的项目,感受着心底的高兴喷涌而出~

接下来,咱们将持续完结和完结其他项目,请坚持期待吧~

版权声明

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