Hi everyone, Is it possible to use compose multip...
# compose-ios
o
Hi everyone, Is it possible to use compose multiplatform on component level, instead of screen level? Here is my use case: I'm developing a chat app using Jetpack Compose on Android and SwiftUI on iOS. We have many chat bubbles, like text, image, voice etc. I want to write these chat bubble components on compose and use them in
List
in SwiftUI. Since it's only possible to create ViewControllers from components, would it be possible and would it make sense to use so many ViewControllers in a List (even if they're very light ViewControllers)
I've managed do work it out somehow but it doesn't calculate the view size correctly.
Copy code
struct TextBubbleFooo: UIViewRepresentable {
    let message: ChatMessage
    
    func makeUIView(context: Context) -> some UIView {
        return TextController(message: message).view
    }
    
    func updateUIView(_ uiView: UIViewType, context: Context) {
        
    }
    
    @available(iOS 16.0, *)
    func sizeThatFits(_ proposal: ProposedViewSize, uiView: UIViewType, context: Context) -> CGSize? {
        return CGSizeMake(100, 100)
    }
}
PrposedViewSize doesn't seem to be correct:
Copy code
COMPOSE proposal: ProposedViewSize(width: Optional(313.0), height: Optional(0.0))
COMPOSE proposal: ProposedViewSize(width: Optional(313.0), height: Optional(inf))
COMPOSE proposal: ProposedViewSize(width: Optional(313.0), height: Optional(93.83333333333334))
COMPOSE proposal: ProposedViewSize(width: Optional(313.0), height: Optional(93.69200000000001))
COMPOSE proposal: ProposedViewSize(width: Optional(313.0), height: nil)
That's why I had to use
UIViewRepresentable
instead of
UIViewControllerRepresentable
. But i don't know how to calculate the view size
o
that's what i did actually but as I said before, the view size is not calculated correctly.
Copy code
struct TextBubbleFoo: UIViewRepresentable {
    let message: String
    
    func makeUIView(context: Context) -> some UIView {
        return TextController(message: message).view
    }
    
    func updateUIView(_ uiView: UIViewType, context: Context) {
        
    }
    
    @available(iOS 16.0, *)
    func sizeThatFits(_ proposal: ProposedViewSize, uiView: UIViewType, context: Context) -> CGSize? {
        return CGSizeMake(proposal.width ?? 100, proposal.height ?? 50)
    }
}


List(0...50,id: \.self){index in
    TextBubbleFoo(message: "\(index).item")
         .padding()
}
Copy code
@Composable
fun TextBubbleCommon(
    message: String
) {
    Column(Modifier.background(Color.Green)) {
        Text(message, color = Color.Blue, modifier = Modifier.padding(20.dp).background(Color.Red))
    }
}

fun TextController(message: String): UIViewController = ComposeUIViewController {
    TextBubbleCommon(message)
}
If I implement sizeThatFits it looks like the first screenshot. If I don't the second screenshot.
e
For now it’s an extremely bad idea! Compose has untrivial machinery backing every instance which is a huge overkill for just rendering a single view.
We’ll explore what we can do for cases like these his and perhaps come to something similar to UITableViewCell hosting configurations
But for now i would avoid doing this
View size is not calculated correctly because for now we don’t have an intrinsic size calculation for Compose view, but I want to investigate what we can do there.
o
Great! Thank you for your answer @Elijah Semyonov I also thought that it wouldn't be performant. I've only come across examples or docs on sharing screens, not components, but I think Compose Multiplatform would be an amazing fit for component level sharing! Hope we will see that feature in the future.
g
@Osman Saral Did you find a solution ? I'm having an issue which I don't know if it's related. I'm trying to use a compose component as a
UICollectionViewCell
in an old
UICollectionView
. The view did appear great the first time but when I navigate back to the screen, The content disappear...
e
We didn’t really test this scenario, so we didn’t assume any specifics on how UICollectionView manages it cells when designing interop. In general we advice to avoid having multiple Compose instances in the same app, but still, please post an issue on YouTrack, so we can triage and consider it for future planning.
g
@Elijah Semyonov Thanks for your reply! As we migrate gradually our iOS app, we'll have more than one compose instance. We hope it'll not be a problem! I'll open an issue on YouTrack
🎉 1
e
It’s not a problem per-se, it’s just that every instance has a significant constant overhead. We’ll eventually try to implement something similar to SwiftUI contentConfiguration, but until then we do not recommend to do so 🙂
g
@Elijah Semyonov Ok thanks, noted! FYI, here is the issue on YT : https://youtrack.jetbrains.com/issue/CMP-5932/View-disappear-in-a-UICollectionView
e
Hey, @Guyaume Tremblay, answered 🙂
🎉 1
o
Hi @Guyaume Tremblay I actually gave up when @Elijah Semyonov told me it wasn't a good idea. Our plan was also migrating gradually. We started by SwiftUI list items but the actual issue was not cells are not displayed, it was the size issue. he also said that "we don't do intrinsic size calculation for Compose view". Even if I take the risk of having performance issues, the size issue is a bigger problem me.
e
“we don’t do intrinsic size calculation for Compose view” so far. I actually want to pick this up soon, just finished some prerequisite tasks 🙂
o
wow that's great. by the way Isn't contentConfiguration is specific to UITableViewCell and UICollectionViewCell? I might want to share different components like a dropdown list, or a simple button one day.
e
I’m speaking about intrinsic size calculation, not efficient instances management 🙂
o
yeah I know. I just added "by the way" at the begining of the sentence 😅
e
Aaah, yeah, they are. But I guess it’s a deliberate solution from the Apple side, because I’m sure they also want to keep the API surface simple.
hostingConfiguration
implies that the actual SwiftUI hosting machinery (which I suppose is something conceptually similar to Compose) resides inside `UITableView`/`UICollectionView` , so the user is only required to assign a single property with
SwiftUI.View
inside.
Maintaining an efficient machinery for a general case, where you want to slap… let’s say
UIHostingView
is exponentially more complicated 🙂
Like, what is visible, what is not, what requires redraws/recalculations, what is occluded. Table and collection views present a rigid framework to reason about those logical constraints.
In practice, it’s not prohibited just to create a bunch of `UIHostingController`s
Just like it’s not prohibited to create a bunch of
ComposeUIViewController
s, not advised, but not restricted either 😏
o
thanks for the explanation
🤞 1
How does @Guyaume Tremblay's demo works without intrinsic size calculation? I guess it has static size
e
Yep, I guess so 🙂
o
collectionViewLayout.estimatedItemSize = CGSize(width: collectionView.bounds.width, height: 100)
yeah
So it wont work if you have dynamic cell sizes @Guyaume Tremblay 😅
e
This one is estimated though, I’m always confused about proper API usage to utilise constraints based automatic size
o
I know right? it was always a big challenge in UIKit, when compared to Android
e
It’s used primarily for calculating scroll bars, because iOS doesn’t know in advance the actual size of the cell
👍 1
And it’s doing a lot of hand holding when scrolling to the item instantly and then scrolling to the top manually, because scroll offset will jump every time the recalculation of cell size above is different from the estimated one
Yeah, in general, a proper management of UIViewController contained inside a UITableViewCell is a bit of a pain even without any Compose attached. Compose makes it harder to do correctly due to CMPViewController lifecycle being managed explicitly by us with some rules, which don’t fit some usages (but I’m already putting some effort into resolving it).
So your best shot is to probably just copy the approach I use… Or instantiate/detach Compose view controller agressively in UITableViewDelegate willShow/endShow.
Just mind the order: pvc.addChild(vc) attachView vc.didMoveToParent(pvc) vc.willMoveToParent(nil) removeView vc.removeFromParent()
👍 1