简体   繁体   中英

ReSwift - How to deal with state changes that depend on old state as well as new state in the View

I am trying to work with ReSwift in my ios project and had a question regarding how to deal with changes in my view. I am finding that I need to know what the old state was before I can apply changes proposed by the new state coming in. I never needed to know what my old state was while working with redux in my react pojects.

My particular use case is, I am bulding a CameraView with an Overlay screen. From anywhere in the app say a ViewController I can create a CameraView and trigger it to open an UIImagePickerController from within it by firing an action. Here's some code:

//ViewController:

class MainViewController: UIViewController {
    var cameraView: CameraView?
    @IBOutlet weak var launchCameraButton: UIButton!

    init() {
        cameraView = CameraView(self)
    }

    @IBAction func launchCameraButtonClicked(_ sender: Any) {
       store.dispatch(OpenCameraAction())
    }

}

//CameraActions
struct OpenCameraAction: Action {}
struct CloseCameraAction: Action {}

//CameraState
struct CameraState {
    var cameraIsVisible: Bool
}

func cameraReducer(state: CameraState?, action: Action) -> CameraState {
    let initialState = state ?? CameraState()

    switch action {
        case _ as OpenCameraAction:
            return CameraState(cameraIsVisible: true)
        default:
            return initialState
    }    
}

//CameraView

class CameraView: StoreSubscriber {
    private var imagePicker: UIImagePickerController?
    weak private var viewController: UIViewController?

    init(viewController: UIViewController) {
        self.viewController = viewController
        super.init()
        imagePicker = UIImagePickerController()
        imagePicker?.allowsEditing = true
        imagePicker?.sourceType = .camera
        imagePicker?.cameraCaptureMode = .photo
        imagePicker?.cameraDevice = .rear
        imagePicker?.modalPresentationStyle = .fullScreen
        imagePicker?.delegate = self
        imagePicker?.showsCameraControls = false

        store.subscribe(self) { subscription in
           subscription.select { state in
                state.camera
           }
        }
    }

    func newState(state: CameraState?) {
        guard let state = state else {
            return
        }
        if state.cameraIsVisible {
            self.open()
        } else if !state.cameraIsVisible {
            self.close()
        }
    }

    func open() {
        if let imagePicker = self.imagePicker {
            self.viewController?.present(
                imagePicker,
                animated: true
            )
        }
    }

    func close(){
         self.imagePicker?.dismiss(animated: true)
    }

}

The above is all the code to open and close the camera. My confusion starts, when we add more actions, such as disable or enable flash. I need to tack on additional state transitions in my view.

My actions now grow to:

struct OpenCameraAction: Action {}
struct CloseCameraAction: Action {}
struct FlashToggleAction: Action {}

My state now looks like this:

struct CameraState {
    var cameraIsVisible: Bool
    var flashOn: Bool
}
// not showing reducer changes as it self explanatory 
// what the state changes will be for my actions.

My View is where the complications start. If the user enabled flash and I am responding to a FlashToggleAction state change, how do I work the state change in my View?

func newState(state: CameraState?) {
        guard let state = state else {
            return
        }

        // this will get triggered regardless of whether
        // a change in the flash happened or not.
        self.toggleFlash(state.flashOn)

        // now the below lines will be executed even though 
        // the only change was a flash on action. 
        //I don't want my camera to opened again or closed.
        if state.cameraIsVisible {
            self.open()
        } else if !state.cameraIsVisible {
            self.close()
        }
    }

How do I respond to changes now? The only way I can think of handling this is by storing a reference to the old state and comparing the difference myself.

In this case, my first question is really: do you need to handle this as part of your app state? Does someone ever need to be notified about the camera state change? If not, keep this as an implementation detail inside your UI layer. let the biew controller open the camera on its own, take a resulting image, then dispatch DoStuffWithImage(imageFromCam) .

This is just a general advice: do not model your UIKit-specific interactions in ReSwift. Do model the data flow that's interesting. Then make the UI components work towards that goal.

Your real question hints at: How do I partition store subscribers?

Currently, you approach the problem using a single subscriber for the camera-related stuff. But you could just as well write 1 subscriber for each independently modifiable component. Like the flash toggle. Then you only have to check for state changes in regard to the flash toggle there, ignoring other settings; although in this case the flash toggle thing could make use of knowing if he camera is actually on -- or you reset the flash state when the camera closes to take care of that", effectively moving the "flash AND camera active" logic down to the reducer.

I can come up with more approaches and ideas, but ultimately it boils down to this: what is a component in your app? Is the camera state control a central piece of the app's state, or just a minute detail? Weighing this early on in the design process can help find a fitting solution.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM