第十九章 TabView|accentColor|AnyView|NavigationView|navigationTitle|navigationBarTit

用户登录之后,就可以进入首页了,我们看一下首页的 UI的样子。

我们先创建一个 HomePage

我们在入口修改逻辑,支持登录完毕进入首页。

TabView 创建 TabBar

我们登录完毕,或者下次启动就进入了首页了。我们首页底部是有 Tab 的,我们需要用到 TabView

我们创建一个 TabPage,用户显示我们首页底部的 Tab

我们修改一下代码,将我们的 HomePage 添加进去。

这显示的效果明显不是我们需要的效果,而且文本怎么变成蓝色了?我们需要的是下面的效果

accentColor设置 TabBar 选中颜色

我们尝试一下设置一下文本的前景色。

但是没有任何的效果,这时候我们需要谷歌一下资料是什么原因了。发现网上一些文章答案已经不能用了,但是 accentColor 这个还是有效果的,但就是废弃了。

需要用最新的 tint(_:),如果想更改默认未选中 item 的颜色,需要通过下面代码设置。

尝试封装 TabView

为了我们的 TabItem 可以方便的进行设置,我们决定封装一下我们 TabView

我们如果封装,需要用户提供试图内容,未选中的图标,选中图标,选中的颜色,未选中的颜色,还有当前选中的索引。

我们需要用户传入一个 TabItem 的数组,我们通过数组进行创建 TabViewitem

但是我们的范型的结构体无法放在数组里面。那么,我们可以将范型设置为 AnyView,这样报错解决了,但是在SwiftUI中最好不要用到 AnyView,这会导致系统无法推断最外层结构,从而无法优化Diff算法,优化性能。

走到这一步,我们发现还是不要封装为好,毕竟超过5个的tabItem就已经少之又少。

我们重新修改一下 TabPage的代码。

⚠️我们使用最新的 .tint(\_:) 会经常不起效果,但是换成 .accentColor(_:)就可以。

对于 TabView 我就先到此为止了,目前也是达到我们的效果,接下来我们开始做我们首页的逻辑。

首页的头部是一个导航条,并且左侧有一个进行选择的选择框。对于导航,我们需要用到 NavigationView

.navigationTitle 设置导航标题

但是我们怎么设置导航标题呢?我们可以在任何子组件通过 .navigationTitle进行设置。

.navigationBarTitleDisplayMode 设置导航样式

但是我们的导航显示是默认的大标题,是符合 iOS新版本的系统风格一样。不过我们可以通过.navigationBarTitleDisplayMode进行设置导航标题的显示模式。

.toolbar 添加导航按钮

此时我们的导航的标题已经显示正常了。但是我们工厂选择的组件怎么添加到首页左侧的位置呢?经过谷歌之后,我们发现可以通过.toolbar的方法轻松的添加左侧和右侧的视图。

我们将添加导航的代码提炼出来,并且设置页面背景颜色为淡灰色。

swift 复制代码
struct HomePage: View {
    @StateObject private var appColor:AppColor = AppColor.share
    var body: some View {
        NavigationView {
            navigationBar {
                Color(uiColor: appColor.c_fefefe)
            }
        }
    }
    
    private func navigationBar<Content:View>(@ViewBuilder content:() -> Content) -> some View {
        content()
            .navigationTitle(Text("首页"))
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement:.navigationBarLeading) {
                    HStack(spacing:6) {
                        Text("请选择工厂")
                            .foregroundColor(Color(uiColor: appColor.c_999999))
                            .font(.system(size: 15))
                        Image("drop_icon")
                    }
                }
            }
    }
}

首页进来需要先获取工厂列表,如果之前设置过并且不在工厂列表,或者都没设置过工厂,则默认为列表的第一个工厂。

我们不管怎么样的逻辑,第一步就是获取工厂列表,之后才能做剩下的逻辑。

swift 复制代码
class HomePageViewModel: BaseViewModel {
    
    /// 请求工厂列表
    func requestFactoryList() async {
        let api = FactoryListApi()
        let model:BaseModel<FactoryListResponseModel> = await request(api: api)
        // 下面逻辑
    }
}

现在我们就拿到了全部的工厂列表,我们判断请求成功就保存工厂列表到当前页面。

swift 复制代码
// 新建一个 @Published 可以接受请求的工厂列表并通知更新
/// 工厂列表
@Published var factoryList:[FactoryListResponseModel] = []
swift 复制代码
// 请求成功就将工厂数据更新
guard model._isSuccess else {return}
factoryList = model.data ?? []

Hashable 解决 ForEach 可能 Sting 相同报错

有了工厂列表我们就可以点击 PopMenuButton 展示所有的工厂列表了。但是我们渲染的时候出现了报错,提示下面的报错。

swift 复制代码
ForEach<Array<String>, String, ModifiedContent<ModifiedContent<PopMenuButtonItem, _BackgroundStyleModifier<BackgroundStyle>>, AddGestureModifier<_EndedGesture<TapGesture>>>>: the ID 111111 occurs multiple times within the collection, this will give undefined results!

提示我们多次出现了数据111111,可能会导致找不到唯一 ID的结果。这样一看,确实是我们当初封装的时候考虑的太浅,没有想到展示的数据可能名字一样,虽然不合理,但是存在。

为了解决这个问题,我们必须对 PopMenuButton 进行重构,我们需要将数据假设成一个协议。

swift 复制代码
protocol PopMenuItem:Hashable {
    /// 显示在 Menu Item 的文字
    var menuTitle:String {get}
}
swift 复制代码
/// old
struct PopMenuButton: View {
    let items:[String]
    @Binding var currentItem:String
    
/// new
struct PopMenuButton<T:PopMenuItem>: View {
    let items:[T]
    @Binding var currentItem:T
swift 复制代码
/// old
typealias ItemValueChanged = (String) -> Void

/// new
typealias ItemValueChanged = (T) -> Void
swift 复制代码
/// old
PopMenuButtonItem(title: item,

/// new
PopMenuButtonItem(title: item.menuTitle,

我们将PopMenButton 的代码修改成如上。但是之前的示例和引用都会报错,对于名字重复出现几率很小,不可能不允许纯文本数组支持。

String 实现 PopMenuItem 实现兼容

为了兼容和支持纯文本数组的支持,我们新增String的扩展。

swift 复制代码
extension String: PopMenuItem {
    var menuTitle: String {self}
}

为了修复我们工厂数据源,因为工厂名字存在重复,我们将 FactoryListResponseModel 实现我们 PopMenuItem 协议。

swift 复制代码
extension FactoryListResponseModel: PopMenuItem {
    var menuTitle: String { factoryName ?? "" }
    /// 重写 == 方法 为了自定义实现两个模型是否一样
    static func ==(lhs:FactoryListResponseModel, rhs:FactoryListResponseModel) -> Bool {
        guard let leftCode = lhs.factoryCode, let rightCode = rhs.factoryCode else {return false}
        return leftCode == rightCode
    }
}

我们调整一下首页的代码,来支持 FactoryListResponseModel 模型。

swift 复制代码
/// HomePageViewMode
/// old
@Published var currentFactoryName:String = ""

/// new
@Published var currentFactory:FactoryListResponseModel = FactoryListResponseModel(factoryCode: nil,
                                                                                      factoryName: nil)

PopMenuButtonModify

swift 复制代码
/// old
struct PopMenuButtonModify: ViewModifier {
    let items:[String]
    @Binding var currentItem:String
    
/// new
struct PopMenuButtonModify<T:PopMenuItem>: ViewModifier {
    let items:[T]
    @Binding var currentItem:T

View+popMenuButton

swift 复制代码
/// old
func popMenuButton(items:[String],
                   currentItem:Binding<String>,
                   isShowPopMenuButton:Binding<Bool>) -> some View{
                   
/// new
func popMenuButton<T:PopMenuItem>(items:[T],
                                  currentItem:Binding<T>,
                                  isShowPopMenuButton:Binding<Bool>) -> some View{

HomePage

swift 复制代码
/// old
.popMenuButton(items: factoryNames,
               currentItem: $viewModel.currentFactoryName,
/// new
.popMenuButton(items: viewModel.factoryList,
                       currentItem: $viewModel.currentFactory,

此时我们工厂选择再也不报错误了,可以正常的显示出来了。

相关推荐
君赏3 小时前
第十七章 @MainActor
swiftui
君赏3 小时前
第十六章 RoundedRectangle|aspectRatio|UIViewRepresentable
swiftui
君赏3 小时前
第十八章 封装HUD和完善登录界面逻辑
swiftui
君赏3 小时前
第十五章 Task|NSAppTransportSecurity|keyDecodingStrategy
swiftui
君赏3 小时前
第十四章 async/await|overlay|PreferencrKey|Anchor
swiftui
君赏3 小时前
第十三章 Button|cornerRadius
swiftui
君赏3 小时前
第六章 Published|ObservedObject|EnvironmentObject|Environment
swiftui
君赏3 小时前
第八章 封装MVVM|onTapGesture|AppStorage
swiftui
君赏3 小时前
第十二章 TextField|EmptyView|SecureField
swiftui