首页的界面基本做完了,功能也挺简单,跳转到对应界面即可。我们就先做一下我的页面的内容,内容也不是很多。

我的页面是一个配置和显示的功能也不是很复杂,但是界面也需要标题栏和灰色的背景试图。但是我们就需要将首页的代码复制一份过来吗?在UIKit的时代,因为是继承关系,我们可以在父类进行设置,但是现在我们在SwiftUI里面。Struct是不能继承的,我们只能封装,使用的时候使用封装的组件来达到效果。
在封装的过程中,遇到了一些困难,差一点就放弃了封装,幸亏找到了解决思路。
遇到的困难就是对于 @ViewBuilder 怎么在初始化提供默认的实现,因为有一些有一些封装不是必须实现的,下面的链接提供了解决的方法。
stackoverflow.com/questions/6...
swift
/// 页面的基础试图
struct PageContentView<Content:View, Leading:View, Trailing:View>: View {
private let title:String
private let content:Content
private let leading:Leading
private let trailing:Trailing
@StateObject private var appColor:AppColor = AppColor.share
/// 初始化页面试图
/// - Parameters:
/// - title: 导航标题
/// - contentBuilder: 内容
/// - leadingBuilder: 导航左侧按钮
/// - trailingBuildeder: 导航右侧按钮
init(title:String,
@ViewBuilder contentBuilder:() -> Content,
@ViewBuilder leadingBuilder:() -> Leading,
@ViewBuilder trailingBuildeder:() -> Trailing) {
self.title = title
self.content = contentBuilder()
self.leading = leadingBuilder()
self.trailing = trailingBuildeder()
}
var body: some View {
navigationBar {
ZStack {
Color(uiColor: appColor.c_efefef)
content
}
}
}
private func navigationBar<Content:View>(@ViewBuilder content:() -> Content) -> some View {
content()
.navigationTitle(Text(title))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement:.navigationBarLeading) {leading}
ToolbarItem(placement:.navigationBarTrailing) {trailing}
}
}
}
extension PageContentView where Leading == EmptyView, Trailing == EmptyView {
init(title:String, contentBuilder:() -> Content) {
self.init(title: title,
contentBuilder: contentBuilder,
leadingBuilder: {EmptyView()},
trailingBuildeder: {EmptyView()})
}
}
我们将刚才封装 PageContentView 用到我们的首页。
swift
struct HomePage: View {
...
var body: some View {
NavigationView {
PageContentView(title: "首页") {
...
} leadingBuilder: {
...
} trailingBuildeder: {
EmptyView()
}
}
...
}
}
我们封装完毕 PageContentView 完毕之后,我们想让登录页面也统一走这个封装试图,我们尝试的修改一下登录界面。
swift
struct LoginPage: View {
...
var body: some View {
PageContentView(title: "登陆") {
VStack {
...
}
.background(.white)
}
...
}
}
我们登陆页面使用 PageContentView 之后,背景颜色会变成灰色,我们重新设置一下内容区域颜色即可。
之前在封装登陆页面的时候,对于外部 HUDView 一致无法封装,我们现在能否封装在 PageContentView 里面呢?
swift
/// 页面的基础试图
struct PageContentView<Content:View,
Leading:View,
Trailing:View,
ViewModel:BaseViewModel>: View {
...
@ObservedObject private var viewModel:ViewModel
...
/// 初始化页面试图
/// - Parameters:
/// - title: 导航标题
/// - contentBuilder: 内容
/// - leadingBuilder: 导航左侧按钮
/// - trailingBuildeder: 导航右侧按钮
init(title:String,
viewModel:ViewModel,
@ViewBuilder contentBuilder:() -> Content,
@ViewBuilder leadingBuilder:() -> Leading,
@ViewBuilder trailingBuildeder:() -> Trailing) {
...
self.viewModel = viewModel
...
}
var body: some View {
navigationBar {
ZStack {
Color(uiColor: appColor.c_efefef)
content
}
.hud(viewModel: $viewModel)
}
}
...
}
extension PageContentView where Leading == EmptyView, Trailing == EmptyView {
init(title:String,
viewModel:ViewModel,
contentBuilder:() -> Content) {
self.init(title: title,
viewModel: viewModel,
contentBuilder: contentBuilder,
leadingBuilder: {EmptyView()},
trailingBuildeder: {EmptyView()})
}
}
我们测试将HUD封装在 PageContentView内部,通过登陆页面调试一切正常。现在我们可以开始做我的页面了。
swift
struct MyPage: View {
@StateObject private var viewModel = MyPageViewModel()
var body: some View {
PageContentView(title: "我的",
viewModel: viewModel) {
Text("Hello, World!")
}
}
}
struct MyPage_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
MyPage()
}
}
}

我的页面基本全是这种左右对齐的控件,我们先制作这个控件。
swift
struct MyCellContentView: View {
@StateObject private var appColor = AppColor.share
var body: some View {
HStack {
Text("车间")
.font(.system(size: 14))
.foregroundColor(Color(uiColor: appColor.c_333333))
Spacer()
HStack {
Text("深圳车间")
.font(.system(size: 14))
.foregroundColor(Color(uiColor: appColor.c_333333))
Image(systemName: "chevron.right")
.foregroundColor(Color(uiColor: appColor.c_cccccc))
}
}
.frame(maxWidth: .infinity)
.padding(EdgeInsets(top: 15, leading: 10, bottom: 15, trailing: 10))
.background(.white)
}
}

我们进行提炼封装,方便后面使用。
swift
struct MyCellContentView<Right:View>: View {
...
private let right:Right
...
init(title:String,
@ViewBuilder rightBuilder:() -> Right) {
self.title = title
self.right = rightBuilder()
}
var body: some View {
HStack {
Text(title)
...
...
right
}
...
}
}
我们使用上面的组件来给我的页面绘制下面的界面

swift
private func userNameCell() -> some View {
MyCellContentView(title: "姓名") {
Text("我的名字")
.font(.system(size: 14))
.foregroundColor(Color(uiColor: appColor.c_cccccc))
}
}
我们这里需要展示用户的昵称,但是我们在用户登录的时候没有保存用户的昵称,我们在登录页面写一下保存的逻辑。
swift
class AppConfig: ObservableObject {
...
@AppStorage("userInfo")
private var userInfo:String?
/// 用户信息
var userInfoModel:UserInfoModel? {
get {
guard let userInfo = userInfo, let jsonData = userInfo.data(using: .utf8) else {
return nil
}
return try? CleanJSONDecoder().decode(UserInfoModel.self, from: jsonData)
}
set {
guard let value = newValue, let jsonData = try? JSONEncoder().encode(value) else {
userInfo = nil
return
}
userInfo = String(data: jsonData, encoding: .utf8)
}
}
}
swift
class LoginPageViewModel: BaseViewModel {
...
func login() async {
...
AppConfig.share.userInfoModel = model.data?.user
}
}
我们已经可以拿到保存的用户名了,我们更新一下刚才视图的代码。
swift
struct MyPage: View {
...
private func userNameCell() -> some View {
MyCellContentView(title: "姓名") {
Text(AppConfig.share.userInfoModel?.userName ?? "")
...
}
}
}
接下来我们制作下面的视图

swift
import SwiftUI
struct MyPage: View {
...
var body: some View {
PageContentView(title: "我的",
viewModel: viewModel) {
VStack {
...
workshopCell()
...
}
}
}
...
private func workshopCell() -> some View {
MyCellContentView(title: "车间") {
HStack {
Text("深圳车间")
.font(.system(size: 14))
.foregroundColor(Color(uiColor: appColor.c_333333))
Image(systemName: "chevron.right")
.foregroundColor(Color(uiColor: appColor.c_cccccc))
}
}
}
}

我们发现 MyCellContentView 组件的下面没有显示横线,这个和我们界面不太一样,我们就修改 MyCellContentView 新增一条线。
swift
struct MyCellContentView<Right:View>: View {
...
var body: some View {
VStack(spacing: 0) {
HStack {
...
}
.frame(maxWidth: .infinity)
.padding(EdgeInsets(top: 15, leading: 10, bottom: 15, trailing: 10))
Rectangle()
.foregroundColor(Color(uiColor: appColor.c_cccccc))
.frame(height: 0.5)
.padding(.leading, 15)
}
.background(.white)
}
}
接下来我们做产线界面

我们发现和刚才做的车间的界面一模一样,我们可以先将车间的进行提炼。
css
struct MyDetailCellContentView: View {
@StateObject private var appColor = AppColor.share
private let title:String
private let detail:String
init(title:String,
detail:String) {
self.title = title
self.detail = detail
}
var body: some View {
MyCellContentView(title: title) {
HStack {
Text(detail)
.font(.system(size: 14))
.foregroundColor(Color(uiColor: appColor.c_333333))
Image(systemName: "chevron.right")
.foregroundColor(Color(uiColor: appColor.c_cccccc))
}
}
}
}
swift
struct MyPage: View {
...
private func workshopCell() -> some View {
MyDetailCellContentView(title: "车间", detail: "深圳车间")
}
}

swift
struct MyPage: View {
...
var body: some View {
PageContentView(title: "我的",
viewModel: viewModel) {
VStack(spacing: 0) {
...
productLineCell()
...
}
}
}
...
private func productLineCell() -> some View {
MyDetailCellContentView(title: "产线", detail: "生产线_yk")
}
}

swift
struct MyPage: View {
...
var body: some View {
PageContentView(title: "我的",
viewModel: viewModel) {
VStack(spacing: 0) {
...
Spacer()
.frame(height:10)
storeHourseCell()
...
}
}
}
...
private func storeHourseCell() -> some View {
MyDetailCellContentView(title: "仓库", detail: "123")
}
}

Toggle 开关
我们自动登录模块稍微有一些不同,我们右侧是一个开关按钮,我们需要用到Toggle。
swift
class AppConfig: ObservableObject {
...
/// 是否自动登录
@AppStorage("isAutoLogin")
var isAutoLogin = false
}
swift
struct MyPage: View {
...
@StateObject private var appConfig = AppConfig.share
var body: some View {
PageContentView(title: "我的",
viewModel: viewModel) {
VStack(spacing: 0) {
...
autoLoginCell()
...
}
}
}
...
private func autoLoginCell() -> some View {
MyCellContentView(title: "自动登录") {
Toggle("", isOn: $appConfig.isAutoLogin)
}
}
}

显示当前版本,这个可以将显示名称的提炼共用一套。
swift
struct MyDetailStyle1CellContentView: View {
@StateObject private var appColor = AppColor.share
private let title:String
private let detail:String
init(title:String,
detail:String) {
self.title = title
self.detail = detail
}
var body: some View {
MyCellContentView(title: title) {
Text(detail)
.font(.system(size: 14))
.foregroundColor(Color(uiColor: appColor.c_cccccc))
}
}
}
swift
struct MyPage: View {
...
var body: some View {
PageContentView(title: "我的",
viewModel: viewModel) {
VStack(spacing: 0) {
...
appVersionCell()
...
}
}
}
private func userNameCell() -> some View {
MyDetailStyle1CellContentView(title: "姓名",
detail: AppConfig.share.userInfoModel?.userName ?? "")
}
...
private func appVersionCell() -> some View {
MyDetailStyle1CellContentView(title: "版本", detail: "1.2.0(1638264135)")
}
}

swift
struct MyPage: View {
...
var body: some View {
PageContentView(title: "我的",
viewModel: viewModel) {
VStack(spacing: 0) {
...
logoutButton()
...
}
}
}
...
private func logoutButton() -> some View {
Button {
} label: {
Text("退出登录")
.frame(maxWidth:.infinity)
.frame(height:45)
.background(Color(uiColor: appColor.c_209090))
.foregroundColor(.white)
.font(.system(size: 16))
.cornerRadius(5)
.padding(30)
}
}
}
至此,我的界面算是全部做出来了。但是界面的数据和交互算是我的页面复杂的部分,虽然显示文本很简单,但是数据的获取十分的麻烦。
我们将我的页面添加到 TabPage里面。
swift
struct TabPage: View {
...
var body: some View {
TabView(selection:$currentTabIndex) {
...
MyPage()
.tabItem {
VStack {
if currentTabIndex == 1 {
Image("我的1")
} else {
Image("我的2")
}
Text("我的")
}
}
.tag(1)
}
...
}
}