compose-multiplatform icon indicating copy to clipboard operation
compose-multiplatform copied to clipboard

Ability to define a UIViewController type

Open GuilhE opened this issue 2 years ago • 11 comments

The absence of a specific type for androidx.compose.ui.window.ComposeUIViewControllerand platform.UIKit.UIViewController presents a limitation in using updateUIViewController and, consequently, the associated Composable.

In scenarios where the iOS application requires management of Composable state through an iOS ViewModel, the availability of such types would prove to be advantageous:

struct SampleUIViewController: UIViewControllerRepresentable {
    
    @Binding var status: String
    let action: () -> Void
    
    func makeUIViewController(context: Context) -> UIViewController {
        return SharedViewControllers().sampleComposable(status: status, click: action)
    }
    
    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
          //how to update the composable state when binding values changes?
    }
}

struct ComposeScreen: View {
    
    @StateObject private var viewModel = ViewModels.sampleViewModel()
    @State private var status: String = ""
    
    var body: some View {
        SampleUIViewController(
            status: $status,
            action: { viewModel.doSomething() }
        )
        .onReceive(viewModel.$state) { new in            
            status = new.label()
        }
        .ignoresSafeArea()
    }
}

I find this use-case quite intriguing, particularly in its potential to attract iOS developers and facilitate incremental adoption. Furthermore, it introduces the exciting possibility of incorporating Composables into a project without the necessity of sharing ViewModels. This offers a more flexible approach compared to a full-fledged Composable + ViewModel sharing strategy.

Currently, the workaround I've discovered involves incorporating a MutableStateFlow within the ComposeUIViewController, which responds to changes in state properties:

object SharedViewControllers {

    private data class ComposeUIViewState(val status: String = "")
    private val state = MutableStateFlow(ComposeUIViewState())

    fun sampleComposable(click: () -> Unit): UIViewController {
        return ComposeUIViewController {
            with(state.collectAsState().value) {
                 Composable(state.status, click)
            }
        }
    }

    fun updateSampleComposable(status: String) {
        state.update { ComposeUIViewState(status = status) }
    }
}

on iosApp side:

struct SampleUIViewController: UIViewControllerRepresentable {
    
    @Binding var status: String
    let action: () -> Void
    
    func makeUIViewController(context: Context) -> UIViewController {
        return SharedViewControllers().sampleComposable(click: action)
    }
    
    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
        SharedViewControllers().updateSampleComposable(status: status)
    }
}

It works, but it's not very graceful.

GuilhE avatar Aug 10 '23 15:08 GuilhE

Thanks for the report. We are currently iterating on API design and real usage examples like this are quite useful.

elijah-semyonov avatar Aug 11 '23 07:08 elijah-semyonov

Thanks for the report. We are currently iterating on API design and real usage examples like this are quite useful.

Hello @elijah-semyonov, you can find here a working sample 😉

GuilhE avatar Aug 11 '23 08:08 GuilhE

@GuilhE What's currently preventing you from using Kotlin Objective-C object (a ViewModel basically), storing MutableState which could be changed inside update method? In this case your setContent could simply reference this object and this mutable state and execute differently based on its value.

elijah-semyonov avatar Aug 30 '23 12:08 elijah-semyonov

@GuilhE What's currently preventing you from using Kotlin Objective-C object (a ViewModel basically), storing MutableState which could be changed inside update method? In this case your setContent could simply reference this object and this mutable state and execute differently based on its value.

I believe that's the work around I'm using, basically I've a Singleton with a Flow/mutableState that the iosApp can call to update the state. The ComposeUIViewController observes this Flow/mutableState and changes accordingly.

GuilhE avatar Aug 30 '23 15:08 GuilhE

We are drifting toward a design, where the state is managed outside of ComposeWindow for multiple reasons (scene destruction on removal from window hierarchy, for example). So it feels like a proper way to do and not just workaround.

elijah-semyonov avatar Aug 31 '23 07:08 elijah-semyonov

Thanks for the article https://proandroiddev.com/compose-multiplatform-managing-ui-state-on-ios-45d37effeda9

dima-avdeev-jb avatar Aug 31 '23 08:08 dima-avdeev-jb

We will discuss it. And try to make better solution for all your needs. But it will take some time.

dima-avdeev-jb avatar Aug 31 '23 08:08 dima-avdeev-jb

Thanks for your work guys and for letting the community help! 🙏🏼

GuilhE avatar Aug 31 '23 09:08 GuilhE

This might come in handy 😇 https://github.com/GuilhE/KMP-ComposeUIViewController

GuilhE avatar Sep 06 '23 02:09 GuilhE

@elijah-semyonov @dima-avdeev-jb latest release changelog:

[1.1.0-ALPHA]
- Adds capability to generate .swift files with UIViewControllerRepresentables.
- Adds script to include those generated files into xcodeproj to be accessible in iOS project;

😇

GuilhE avatar Sep 08 '23 17:09 GuilhE

@elijah-semyonov @dima-avdeev-jb

Hello guys! Just dropping by to share news about my new gradle plugin that simplifies the library setup.

Example:

plugins {
    id("io.github.guilhe.kmp.plugin-composeuiviewcontroller") version "1.1.0"
}

ComposeUiViewController {
    iosAppName = "Gradient"
    targetName = "Gradient"
}

With this, all the code generation and export is handled automatically, no more complex configurations needed! 😊

GuilhE avatar Jun 28 '24 17:06 GuilhE

Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.

okushnikov avatar Aug 26 '24 13:08 okushnikov