第二十四章 init 方法初始化 State

4 阅读4分钟

选择车间功能做完之后,我们接下来开始做产线的功能。

image-20211206160005636转存失败,建议直接上传图片文件

但是产线的功能来源于车间,意思当车间更换之后,我们的产线就要发生变更。那么我们就要监听AppConfigworkShopCode 值发生改变,我们请求产线的数据。

但是不幸的是,我们的 workShopCode 不是通过 @Published修饰的,所以我们无法通过sink方式监听值的变更。既然我们只能通过 @Published 进行监听,我们将之前值设置为中间代码。

class AppConfig: ObservableObject {
    ...
    /// 选中的车间代码
    @AppStorage("workShopCode")
    /// 因为 _workShopCode 已经被系统使用 我们用workShopCode_
    private var workShopCode_:String?
    
    /// 用于外部监听 workShopCode 值的变更
    @Published var workShopCode:String? {
        didSet {
        		/// 车间切换 更新本地缓存
            workShopCode_ = workShopCode
        }
    }
		...
    
    init() {
        workShopCode = workShopCode_
    }
}
class MyPageViewModel: BaseViewModel {
    ...
    /// 保存 AnyCancellable 不然会导致后续的车间变更获取不到通知
    private var workshopCancellabel:AnyCancellable?
    override init() {
        super.init()
        workshopCancellabel = AppConfig.share.$workShopCode.sink {[weak self] value in
            /// 监听到 车间变更
            print(value ?? "")
        }
    }
    ...
}

但是在运行测试过程中,发现了一个问题。就是我们来回切换选择器值的时候,我们也收到了值更新的打印。那么我们确定的按钮根本没有起到作用。

我们功能是只有当用户点击了确定按钮的时候,我们才需要更改外部值。

struct PickerSheet<Item:DataPickerItem>: View {
    ...
    /// 只存在 PickerSheet 运行期间缓存的值 解决只有点击确定按钮才更新外部的值的问题
    @State private var cacheSelectItem:Item?
    ...
    var body: some View {
        VStack {
            ...
            DataPickerView(items: items, selectItem: $cacheSelectItem)
            ...
        }
        ...
    }
    
    private func confirmClick() {
        selectItem = cacheSelectItem
        confirmHandle()
    }
}

但是我们测试的过程中发现,每次点击弹出 PickerSheet 组件的时候,我们默认选择的第一项都是第一个。看来之前写的代码不生效?这个是什么原因导致的呢?

通过断点查看,原来 selectItem 传进来为空,想起来了,这个值默认为空,之后我们就没管了。我们需要获取到 workShops 数据之后,将之前选中的车间简码转换成对应的模型。

class MyPageViewModel: BaseViewModel {
    ...
    private func getAllWorkShop() async {
        ...
        currentWorkshop = data.first(where: { response in
            guard let configCode = AppConfig.share.workShopCode else {return false}
            guard let code = response.workshopCode else {return false}
            return configCode == code
        })
    }
    ...
}

发现上述代码改完之后,我们在测试过程中,发现还是空值,后来一想,我们使用了 cacheSelectItem 作为中间值,是因为中间值没有初始化。

struct PickerSheet<Item:DataPickerItem>: View {
    ...
    init(...) {
        ...
        self.cacheSelectItem = selectItem.wrappedValue
    }
    ...
}

在 Init 方法初始化 State

这样改后依然没有任何的效果,难道是因为在init中还没有初始化State,我们在init 自己初始化试一下。

struct PickerSheet<Item:DataPickerItem>: View {
    ...
    init(...) {
        ...
        self._cacheSelectItem = State(initialValue: selectItem.wrappedValue)
    }
    ...
}

通过上述的代码,我们 PickerSheet 弹出之后选中对应不会选中对应行的问题解决了。对于属性包装器在 init 方法完毕才初始化,这个确实是不注意就坑的地方。

接下来,我们来写获取车间下面所有产线的方法。

class MyPageViewModel: BaseViewModel {
    ...
    override init() {
        ...
        workshopCancellabel = AppConfig.share.$workShopCode.sink {[weak self] value in
            /// 监听到 车间变更
            Task {[weak self] in
                guard let self = self else {return}
                await self.getAllProductLine()
            }
        }
    }
    
    ...
    
    /// 获取车间下面的所有产线
    private func getAllProductLine() async {
        guard let workShopCode = AppConfig.share.workShopCode else {
            showHUDMessage(message: "请选选择车间!");
            return
        }
        let api = GetAllProductLineApi(workshopCode: workShopCode)
        let model:BaseModel<[GetAllProductLineApiResponse]> = await request(api: api, showHUD: false)
        guard model._isSuccess else {return}
        guard let data = model.data else {return}
        productLines = data
    }
    ...
}

我们根据切换车间,就重新获取新的产线,设置新的产线。

image-20211207140305370转存失败,建议直接上传图片文件

为了可以获取到之前保存的产线code,那么我们需要在 AppConfig新增一个变量。

class AppConfig: ObservableObject {
    ...
    /// 选中的产线 code
    @AppStorage("productLineCode")
    var productLineCode:String?
    
		...
}

我们在获取产线列表方法,实现上面的流程图。

class MyPageViewModel: BaseViewModel {
    ...
    
    /// 车间下面所有的产线列表
    var productLines:[GetAllProductLineApiResponse] = []
    ...
    /// 获取车间下面的所有产线
    private func getAllProductLine() async {
        ...
        /// 是否存在之前选中保存的产线code
        if let productLineCode = AppConfig.share.productLineCode {
            /// 存在 判断在最新产线列表是否存在 就将之前的 code 进行更新
            if isExit(productLine: productLineCode, in: productLines) {
                AppConfig.share.productLineCode = productLineCode
            } else {
                /// 否则就默认第一个产线
                AppConfig.share.productLineCode = productLines.first?.code
            }
        } else {
            /// 如果不存在之前选中保存的产线 code 则直接默认第一个产线
            AppConfig.share.productLineCode = productLines.first?.code
        }
    }
    
    
    /// 是否指定产线的 code 在列表存在
    /// - Parameters:
    ///   - code: 产线的 code
    ///   - list: 产线列表
    private func isExit(productLine code:String, in list:[GetAllProductLineApiResponse]) -> Bool {
        /// 查找出指定产线 code 在列表位置
        let index = list.firstIndex(where: { response in
            guard let _code = response.code else {return false}
            return code == _code
        })
        /// 如果查找出来索引不为空,则代表存在于列表中
        return index != nil
    }
    
    ...
}

为了可以拿到当前选中产线的模型,用于显示当先选中产线的名字,我们修改一下代码。

class MyPageViewModel: BaseViewModel {
    ...
    
    /// 当前选中产线的模型
    @Published var currentProductLine:GetAllProductLineApiResponse? {
        didSet {
            AppConfig.share.productLineCode = currentProductLine?.code
        }
    }
    
    ...
    
    /// 获取车间下面的所有产线
    private func getAllProductLine() async {
        ...
        
        /// 获取列表之后 找到当前选中的模型
        currentProductLine = productLines.first(where: { response in
            guard let code = AppConfig.share.productLineCode, let _code = response.code else {return false}
            return code == _code
        })
    }
    ...
}

我们这样写逻辑也没有问题,但是自信的一想,最后一个获取当前选中模型赋值操作之后再次更新了 AppConfigproductLineCode的值。

image-20211207144826949转存失败,建议直接上传图片文件

此时我们有四条线可以更新产线的code,灰色区域三种只存在一种可能,加上外面可以更新产线code。整个流程下面会因为设置当前产线模型多了一次更新。

虽然性能方面不会产生大的影响,但是后续如果其他地方监听产线变更做一些逻辑,就会导致问题出现。我们修改一下逻辑图如下所示。

image-20211207151724845转存失败,建议直接上传图片文件

这样我们就还是三种只存在一种可能设置产线 Code,我们修改一下逻辑代码。

class MyPageViewModel: BaseViewModel {
    ...
    
    /// 获取车间下面的所有产线
    private func getAllProductLine() async {
        ...
        /// 是否存在之前选中保存的产线code
        if let productLineCode = AppConfig.share.productLineCode {
            /// 存在 判断在最新产线列表是否存在 就将之前的 code 进行更新
            if let response = find(productLine: productLineCode, in: productLines) {
                currentProductLine = response
            } else {
                /// 否则就默认第一个产线
                currentProductLine = productLines.first
            }
        } else {
            /// 如果不存在之前选中保存的产线 code 则直接默认第一个产线
            currentProductLine = productLines.first
        }
    }
    
    
    /// 根据产线 code 从产线列表查找对应模型
    /// - Parameters:
    ///   - code: 产线 code
    ///   - list: 产线列表
    /// - Returns: 对应模型
    private func find(productLine code:String, in list:[GetAllProductLineApiResponse]) -> GetAllProductLineApiResponse? {
        return list.first { response in
            guard let _code = response.code else { return false }
            return code == _code
        }
    }
    
    ...
}

我们已经拿到了当前选中的产线,我们将显示在当前页面上面。

struct MyPage: View {
    ...
    private func productLineCell() -> some View {
        MyDetailCellContentView(title: "产线",
                                detail: viewModel.currentProductLine?.name ?? "请选择产线")
    }
    
    ...
}

转存失败,建议直接上传图片文件

我们切换车间的时候,产线也随之发生了改变。我们此时产线无法进行手动选择,我们添加一下功能。

struct MyPage: View {
    @StateObject private var viewModel = MyPageViewModel()
    ...
    @StateObject private var appConfig = AppConfig.share
    var body: some View {
        PageContentView(title: "我的", viewModel: viewModel) {
            VStack(spacing: 0) {
                ...
                productLineCell()
                    .onTapGesture(perform: didClickProductLine)
                ...
            }
        }
        ...
    }
    
    ...
    
    /// 点击了产线
    private func didClickProductLine() {
        guard let _ = appConfig.workShopCode else {
            viewModel.showHUDMessage(message: "请先选择车间!");
            return
        }
        DataPickerManager.manager.show {
            PickerSheet(title: "产线",
                        items: viewModel.productLines,
                        selectItem: $viewModel.currentProductLine) {
                DataPickerManager.manager.dismiss()
            } confirmHandle: {
                DataPickerManager.manager.dismiss()
            }
        }
    }
}

为了不让调用设置背景色,我们将设置白色背景设置在 PickerSheet 里面。

struct PickerSheet<Item:DataPickerItem>: View {
    ...
    
    var body: some View {
        VStack {
            ...
        }
        ...
        .background(.white)
    }
    
    ...
}

转存失败,建议直接上传图片文件

得意于我们之前封装,产线功能做起来才会这么顺手和快速。接下来我们来做选择仓库的功能,我们只需要获取仓库列表,之后设置仓库对应的 code

image-20211207161639977转存失败,建议直接上传图片文件

struct MyPage: View {
    @StateObject private var viewModel = MyPageViewModel()
    ...
    var body: some View {
        PageContentView(title: "我的", viewModel: viewModel) {
            VStack(spacing: 0) {
                ...
                storeHourseCell()
                    .onTapGesture(perform: didClickStoreHourse)
                ...
            }
        }
        ...
    }
    
    ...
    private func storeHourseCell() -> some View {
        MyDetailCellContentView(title: "仓库",
                                detail: viewModel.currentStoreHouse?.name ?? "请选择仓库")
    }
    
    ...
    private func didClickStoreHourse() {
        DataPickerManager.manager.show {
            PickerSheet(title: "仓库",
                        items: viewModel.storeHouses,
                        selectItem: $viewModel.currentStoreHouse) {
                DataPickerManager.manager.dismiss()
            } confirmHandle: {
                DataPickerManager.manager.dismiss()
            }

        }
    }
}
class MyPageViewModel: BaseViewModel {
    ...
    /// 仓库列表
    var storeHouses:[GetAllStoreHouseApiResponse] = []
    
    /// 当前选中的仓库
    @Published var currentStoreHouse:GetAllStoreHouseApiResponse? {
        didSet {
            AppConfig.share.storeHouseCode = currentStoreHouse?.code
        }
    }
    
    ...
    
    public func initData() async {
        ...
        /// 仓库列表的数据只和工厂有关系 所以可以放在初始化进行请求
        await getAllStoreHouse()
    }
    
    ...
    
    func getAllStoreHouse() async {
        let api = GetAllStoreHouseApi()
        let model:BaseModel<[GetAllStoreHouseApiResponse]> = await request(api: api, showHUD: false)
        guard model._isSuccess, let data = model.data else {return}
        storeHouses = data
        /// 是否存在之前保存过的仓库
        if let storeHouseCode = AppConfig.share.storeHouseCode {
            if let response = find(storeHouse: storeHouseCode, in: storeHouses) {
                currentStoreHouse = response
            } else {
                /// 如果最新的仓库列表已经不包含之前选中的仓库 则默认第一个仓库
                currentStoreHouse = storeHouses.first
            }
        } else {
            /// 如果不存在 则设置默认第一个仓库
            currentStoreHouse = storeHouses.first
        }
    }
    
    ...
    /// 根据仓库 code 从仓库列表查找对应模型
    /// - Parameters:
    ///   - code: 仓库 code
    ///   - list: 仓库列表
    /// - Returns: 查找的模型
    private func find(storeHouse code:String, in list:[GetAllStoreHouseApiResponse]) -> GetAllStoreHouseApiResponse? {
        return list.first { response in
            guard let _code = response.code else { return false }
            return code == _code
        }
    }
    
    ...
}
struct GetAllStoreHouseApi {}

extension GetAllStoreHouseApi: APIConfig {
    var path: String { "/api/winplus/bm/store/search" }
}
struct GetAllStoreHouseApiResponse: Codable {
    /// 仓库名称
    let name:String?
    /// 仓库 code
    let code:String?
}

extension GetAllStoreHouseApiResponse: DataPickerItem {
    var pickerItemTitle: String { name ?? "" }
    
    static func ==(lhs:GetAllStoreHouseApiResponse, rhs:GetAllStoreHouseApiResponse) -> Bool {
        guard let code = lhs.code, let _code = rhs.code else { return false }
        return code == _code
    }
}

转存失败,建议直接上传图片文件