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

I am trying to work with ReSwift on my ios project and have asked a question on how to handle changes in my view. I find that I need to know what the old state is before I can apply the changes proposed by the new state. I never needed to know what my old state was when dealing with redux in my reactions.

My specific use case: I am zooming in on a CameraView using the Overlay screen. From anywhere in the application it can be said that the ViewController I can create a CameraView and run it to open the UIImagePickerController from the inside by triggering the action. Here is the 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 code is the code for opening and closing the camera. My confusion starts when we add more actions like disable or enable flash. In my opinion I need to stick with additional state transitions.

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.

      

In my mind, complications begin. If the user has Flash enabled and I am responding to a state change FlashToggleAction, how do I change 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 react to changes now? The only way I can handle this is to keep a reference to the old state and compare the difference myself.

+3


source to share


1 answer


In this case, my first question is really: do you need to handle this as part of your application's state? Does anyone ever need to be notified of a change in camera state? If not, keep this as an implementation detail inside your UI. let the biew controller open the camera by itself, take the resulting image, then send DoStuffWithImage(imageFromCam)

.

This is just a general tip: don't model your UIKit-specific interactions in ReSwift. Make your data flow model interesting. Then create UI components to achieve this goal.

Your real question dictates: How can I break down store subscribers?



You are currently approaching the problem of using one caller for camera-related material. But you could also write 1 subscriber for each independently modifiable component. Like a flash. Then you only need to check for state changes with respect to the flash switch, ignoring other settings; although in this case the flash switch can use knowledge if it is actually on, or you reset the flash state when the camera shuts down to take care of it, "effectively moving the flash AND camera active" logic down towards the reducer.

I can think of more approaches and ideas, but it ultimately boils down to this: what is a component of your application? Does the camera state control the central part of the application state, or just the smallest detail? Weighing this at the beginning of the design process can help find a suitable solution.

+2


source







All Articles