第二十五章 完善登录逻辑

实现自动登录

接下来我们需要做 `自动登陆功能,自动登陆就是登陆之后,下次启动开启状态下,直接进入首页。关闭情况下,则进入登陆页面。

swift 复制代码
@main
struct Win_App: App {
    @StateObject private var appConfig:AppConfig = AppConfig.share
    var body: some Scene {
        WindowGroup {
            if isLogin {
                if appConfig.isAutoLogin {
                    TabPage()
                } else {
                    LoginPage()
                }
            } else {
                LoginPage()
            }
        }
    }
    
    ...
}

我们需要两处需要初始化LoginPage的地方,这个玩意需要参数,或者其他设置,就比较麻烦了,虽然我们可以提炼代码。

swift 复制代码
@main
struct Win_App: App {
    @StateObject private var appConfig:AppConfig = AppConfig.share
    var body: some Scene {
        WindowGroup {
            if isLogin {
                if appConfig.isAutoLogin {
                    TabPage()
                } else {
                    loginPage()
                }
            } else {
                loginPage()
            }
        }
    }
    
    ...
    
    private func loginPage() -> some View {
        LoginPage()
    }
}

但是我们的判断逻辑依然十分的复杂,我们可以变更一下流程图。

我们将判断的逻辑封装成一个方法,这样虽然看起来没啥变化,但是对于页面处理逻辑清晰。

swift 复制代码
@main
struct Win_App: App {
    @StateObject private var appConfig:AppConfig = AppConfig.share
    var body: some Scene {
        WindowGroup {
            if isNeedLogin {
                LoginPage()
            } else {
                TabPage()
            }
        }
    }
    
    ...
    
    private var isNeedLogin:Bool {
        return !isLogin || !appConfig.isAutoLogin
    }
    
}

但是,经过测试,我们登陆成功也是无法进入首页的,因为 isAutoLogin 默认关闭的。经过思考,我们上面的逻辑是有问题的,需要修改一些逻辑。

1 当App全新未安装的时候(红线代表逻辑走向)

2 当执行登陆完毕之后

3 第二次启动 App已经登录过 但是没有开启自动登录

4 App启动 App已经登录过,开启了自动登录

我们按照流程图写一下代码

swift 复制代码
@main
struct Win_App: App {
    @StateObject private var appConfig:AppConfig = AppConfig.share
    ...

    private var isNeedLogin:Bool {
        /// 如果 gatewayUserName 不存在 则需要进行登录
        guard isExitGatewayUserName else { return true}
        /// 如果 gatewayUserName 存在 并且 isGatewayUserNameFromCache = false 代表是刚刚登录的 则不需要登录
        guard appConfig.isGatewayUserNameFromCache else { return false }
        /// 如果 gatewayUserName 存在 并且 isGatewayUserNameFromCache = true 代表登录是之前运行操作的 如果没开启自动登录就需要前往重新登录
        return !appConfig.isAutoLogin
    }
    
    /// 是否存在 gatewayUserName
    private var isExitGatewayUserName:Bool {
        guard let gatewayUserName = appConfig.gatewayUserName else { return false }
        return !gatewayUserName.isEmpty
    }
}
swift 复制代码
class AppConfig: ObservableObject {
    ...
    
    /// gatewayUserName 是否来源于缓存 默认来源于缓存
    var isGatewayUserNameFromCache:Bool = true
    
    ...
}
swift 复制代码
class LoginPageViewModel: BaseViewModel {    
    ...
    func login() async {
        ...
        if let gatewayUserName = model.data?.gatewayUserName {
            /// 放在 [AppConfig.share.gatewayUserName] 赋值的前面 这样 gatewayUserName 通知时候才能获取 isGatewayUserNameFromCache 最新值
            AppConfig.share.isGatewayUserNameFromCache = false
            AppConfig.share.gatewayUserName = gatewayUserName
        }
        ...
    }
}

接下来我们需要获取版本号和 build号显示出来,这个简单一些。

swift 复制代码
struct MyPage: View {
    ...
    
    private func appVersionCell() -> some View {
        MyDetailStyle1CellContentView(title: "版本",
                                      detail: viewModel.versionValue)
    }
    
    ...
}
swift 复制代码
class MyPageViewModel: BaseViewModel {
    ...
    
    var versionValue:String {
        guard let infoDictionary = Bundle.main.infoDictionary else { return "" }
        guard let version = infoDictionary["CFBundleShortVersionString"] else { return "" }
        guard let buildNumber = infoDictionary["CFBundleVersion"] else { return "" }
        return "\(version)(\(buildNumber))"
    }
}

我的页面接下来就只剩下退出登录功能了,我们按照我们上方登录流程图来看,只需要将 gatewayUserName 设置为 nil即可实现退出登录,回到登录界面。

swift 复制代码
struct MyPage: View {
    ...
    @StateObject private var appConfig = AppConfig.share
    ...
    
    private func logoutButton() -> some View {
        Button {
            appConfig.gatewayUserName = nil
        } label: {
            ...
        }

    }
    
    ...
}

研究界面的初始化和重建

但是我们重新进来还是在我的界面,既然重新登录,我认为就应该回到首页。我们在研究生命周期时候发现下面的打印。

swift 复制代码
struct TabPage: View {
    
    ...
    
    init() {
        print("-> TabPage init")
        ...
    }
    
    var body: some View {
        TabView(selection:$currentTabIndex) {
            ...
        }
        ...
        .onAppear {
            print("-> currentTabIndex = \(currentTabIndex)")
        }
    }
}
shell 复制代码
-> LoginPage init // 未登录展示登录界面
-> TabPage init // 登陆 初始化TabPage
-> HomePage init // 初始化HomePage
-> MyPage init // 初始化 MyPage
-> currentTabIndex = 0 // TabPage onAppear
-> TabPage init // ??? 为啥再次初始化一次
-> HomePage init /// 点击currentTabIndex = 1 重新初始化HomePage
-> MyPage init // 重新初始化 MyPage
-> TabPage init // ??? 不理解为啥再次初始化
-> TabPage init // ??? 不理解为啥再次初始化
-> TabPage init // ??? 不理解为啥再次初始化
-> TabPage init // ??? 不理解为啥再次初始化

TabPage init打印了很多次,但是 currentTabIndex 只打印了一次,那就是 onAppear只执行了一次。我们简单绘制一下渲染树结构,按照Page为单位。

我们通过 @State 将数结构细化一点

从首页切换到我的页面,为啥切换到我的页面会打印这么多次 TabPage init?我的页面和首页的不同就是,首页初始化了工厂列表,我的页面在onAppear方法里面执行了初始化车间和产线还有仓库数据的操作。

难道和这个有关系,我们屏蔽一下初始化的代码。

swift 复制代码
struct MyPage: View {
    ...
    var body: some View {
        ...
        return PageContentView(title: "我的", viewModel: viewModel) {
            ...
            }
        }
        .onAppear {
//            Task {
//                await viewModel.initData()
//            }
        }
    }
    
    ...
}
swift 复制代码
struct HomePage: View {
    ...
    var body: some View {
        return NavigationView {
            ...
        }
        ...
        .onAppear {
//            Task {
//                await viewModel.requestFactoryList()
//            }
        }
    }
}

我们再次看一下日志输出。

shell 复制代码
-> LoginPage init // 未登录展示登录界面
-> TabPage init // 登陆 初始化TabPage
-> HomePage init // 初始化HomePage
-> MyPage init // 初始化 MyPage
-> currentTabIndex = 0 // TabPage onAppear
-> HomePage init /// 点击currentTabIndex = 1 重新初始化HomePage
-> MyPage init // 重新初始化 MyPage
-> TabPage init // ??? 不理解为啥再次初始化

少了四次 TabPage init,这四次应该就是刷新我的界面的 车间/产线/仓库显示和刷新首页工厂操作引起的。但是这样依然 View 初始化的很多次,按照我们的操作。

shell 复制代码
/// 下面是理想状态下的输出
-> LoginPage init // 未登录展示登录界面
-> TabPage init // 登陆 初始化TabPage
-> HomePage init // 初始化HomePage
-> MyPage init // 初始化 MyPage
-> currentTabIndex = 0 // TabPage onAppear

上面应该就是在 UIKit系统下面正常的数据,但是SwiftUI不同于UIKit的生命周期,但是和Flutter有类似的作用。我们打印一下Body执行的过程,这个才是真正设计到调用绘制。

swift 复制代码
-> LoginPage init /// 未登录 初始化 LoginPage
-> LoginPage Body /// 绘制 LoginPage
-> LoginPage Body /// 展示 Loading 绘制 LoginPage
-> LoginPage Body /// 展示登陆成功提示 绘制 LoginPage
-> TabPage init /// 登陆成功 初始化 TabPage
->Tab Page Body /// 绘制 TabPage
->HomePage init /// 初始化 HomePage
->MyPage init /// 初始化 MyPage 因为都没展示我的页面 所以后续不需要绘制
-> currentTabIndex = 0 /// TabPage onAppear
-> HomePage Body /// 绘制 HomePage
->Tab Page Body /// 点击 tab = 1 重新绘制 TabPage
->HomePage init /// 重新初始化 HomePage 因为首页已经绘制 所以不需要重新绘制
->MyPage init /// 重新初始化 MyPage
->MyPage Body /// 绘制 MyPage 页面
->MyPage Body /// 重新绘制 MyPage 页面
-> TabPage init /// 初始化 TabPage

从输出上面看绘制首页一次是正常的,虽然多次初始化,多次初始化对于性能影响不大。但是我的页面绘制了两次?经过不停的调试,发现我的页面比首页多执行一次的原因在于 在 HomePage中添加了 NavigationView, 而 MyPageNavigationView 是加在 TabPage里面的。

我们都将 NavigationView 转移到 TabPage,再次看一下输出。

swift 复制代码
-> LoginPage init
-> LoginPage Body
-> LoginPage Body
-> LoginPage Body
-> TabPage init
->Tab Page Body
->HomePage init
->MyPage init
-> currentTabIndex = 0
-> HomePage Body
->Tab Page Body
->HomePage init
->MyPage init
->MyPage Body
->MyPage Body
-> TabPage init

都转移出来之后,发现刚开始进入的时候就开始初始化了 我的页面了。

我们能够通过树形结构局部刷新数来优化呢?答案是肯定的,但是目前来说也没必要研究那么深入,并且现在的页面就算优化,也没有大的意义。

从上面的输入看,当页面重新初始化和绘制的时候,@State不会随着初始化的,导致我们重新登陆完毕,展示给我们的是我的界面的问题。

因为 @StateTabPage私有的,所以我们在我的页面退出登录也无法操作 TabPagecurrentTabIndex。目前想到了两种方案,第一种采用通知的形式,第二种采用@Binding。对于Struct,我猜测通知的方式可能不生效,或者麻烦,没有@Binding 方便。

swift 复制代码
class AppConfig: ObservableObject {
		...
    /// 当前 Tab 的索引
    @Published var currentTabIndex:Int = 0
    
    ...
}
swift 复制代码
struct TabPage: View {
    ...
    @StateObject private var appConfig = AppConfig.share
    ...
    var body: some View {
        TabView(selection:$appConfig.currentTabIndex) {
            ...
        }
        ...
    }
}

我们采用在AppConfig中新增一个@Published标识当前选中的Tab,因为AppConfig对象随时可以访问。为了修复重新登录无法重新定位到首页,我们在退出登录重置一下 currentTabIndex

swift 复制代码
struct MyPage: View {
    ...
    @StateObject private var appConfig = AppConfig.share
    ...
    private func logoutButton() -> some View {
        Button {
            ...
            appConfig.currentTabIndex = 0
        } label: {
            ...
        }

    }
}
相关推荐
君赏4 小时前
第二十六章 Focused
swiftui
君赏4 小时前
第 二十章 @Published sink
swiftui
君赏4 小时前
第二十一章 @ViewBuilder默认实现|Toggle|我的页面封装
swiftui
君赏4 小时前
第二十九章 修复首页 PopMenuView 显示问题
swiftui
君赏4 小时前
第二十八章 重置 ObservableObject 模型数据
swiftui
君赏4 小时前
第二十七章 UINavigationBarAppearance|Divider
swiftui
君赏4 小时前
第二十二章 onAppear|DataPickerView
swiftui
君赏4 小时前
第二十三章 UIHostingController|withAnimation|SwiftUI 默认动画时间
swiftui
君赏4 小时前
第二十四章 init 方法初始化 State
swiftui