Material Design components for Compose Multiplatform, specifically optimized for iOS devices running on ARM64 architecture
—
Seamless integration with native iOS UIKit components and platform-specific behaviors, enabling Material Design components to work harmoniously with iOS native development patterns.
Embed native iOS UIKit views directly within Compose Material layouts.
/**
* Composes a UIView provided by `factory` into a Compose UI hierarchy
* @param factory A function which is called to create the UIView
* @param modifier Modifier to be applied to this UIKitView
* @param update A lambda which is called whenever the UIKitView is recomposed
* @param onResize Called when the UIView's size changes
* @param onRelease Called when the UIKitView is removed from the composition
*/
@Composable
fun <T : UIView> UIKitView(
factory: () -> T,
modifier: Modifier = Modifier,
update: (T) -> Unit = {},
onResize: ((T, CGRect) -> Unit)? = null,
onRelease: (T) -> Unit = {}
)Usage Examples:
// Embed a native iOS text field
@Composable
fun NativeTextField(
text: String,
onTextChange: (String) -> Unit,
placeholder: String = ""
) {
UIKitView(
factory = {
UITextField().apply {
this.placeholder = placeholder
borderStyle = UITextBorderStyle.UITextBorderStyleRoundedRect
// Set up text change listener
addTarget(
target = object : NSObject() {
@ObjCAction
fun textChanged() {
onTextChange(text ?: "")
}
},
action = NSSelectorFromString("textChanged"),
forControlEvents = UIControlEvents.UIControlEventEditingChanged
)
}
},
update = { textField ->
textField.text = text
},
modifier = Modifier
.fillMaxWidth()
.height(44.dp)
)
}
// Embed a native iOS map view
@Composable
fun NativeMapView(
latitude: Double,
longitude: Double,
modifier: Modifier = Modifier
) {
UIKitView(
factory = {
MKMapView().apply {
showsUserLocation = true
userInteractionEnabled = true
}
},
update = { mapView ->
val coordinate = CLLocationCoordinate2DMake(latitude, longitude)
val region = MKCoordinateRegionMake(
coordinate,
MKCoordinateSpanMake(0.01, 0.01)
)
mapView.setRegion(region, animated = true)
},
modifier = modifier
)
}
// Embed iOS camera preview
@Composable
fun CameraPreview(
modifier: Modifier = Modifier,
onCapturePhoto: () -> Unit = {}
) {
UIKitView(
factory = {
UIView().apply {
backgroundColor = UIColor.blackColor
// Set up AVCaptureSession here
}
},
modifier = modifier
)
}Embed complete UIKit view controllers within Compose layouts.
/**
* Composes a UIViewController provided by `factory` into a Compose UI hierarchy
* @param factory A function which is called to create the UIViewController
* @param modifier Modifier to be applied to this UIKitViewController
* @param update A lambda which is called whenever the UIKitViewController is recomposed
* @param onRelease Called when the UIKitViewController is removed from the composition
*/
@Composable
fun <T : UIViewController> UIKitViewController(
factory: () -> T,
modifier: Modifier = Modifier,
update: (T) -> Unit = {},
onRelease: (T) -> Unit = {}
)Usage Examples:
// Embed iOS document picker
@Composable
fun DocumentPicker(
onDocumentSelected: (URL) -> Unit,
modifier: Modifier = Modifier
) {
var showPicker by remember { mutableStateOf(false) }
if (showPicker) {
UIKitViewController(
factory = {
UIDocumentPickerViewController(
documentTypes = listOf("public.data"),
mode = UIDocumentPickerMode.UIDocumentPickerModeImport
).apply {
delegate = object : UIDocumentPickerDelegate {
override fun documentPicker(
controller: UIDocumentPickerViewController,
didPickDocumentsAt: List<URL>
) {
didPickDocumentsAt.firstOrNull()?.let { url ->
onDocumentSelected(url)
}
showPicker = false
}
override fun documentPickerWasCancelled(
controller: UIDocumentPickerViewController
) {
showPicker = false
}
}
}
},
modifier = modifier
)
}
// Trigger button
Button(onClick = { showPicker = true }) {
Text("Select Document")
}
}
// Embed iOS image picker
@Composable
fun ImagePicker(
onImageSelected: (UIImage) -> Unit,
modifier: Modifier = Modifier
) {
var showImagePicker by remember { mutableStateOf(false) }
if (showImagePicker) {
UIKitViewController(
factory = {
UIImagePickerController().apply {
sourceType = UIImagePickerControllerSourceType.UIImagePickerControllerSourceTypePhotoLibrary
delegate = object : UIImagePickerControllerDelegate, UINavigationControllerDelegate {
override fun imagePickerController(
picker: UIImagePickerController,
didFinishPickingMediaWithInfo: Map<Any?, Any?>
) {
val image = didFinishPickingMediaWithInfo[UIImagePickerControllerOriginalImage] as? UIImage
image?.let { onImageSelected(it) }
showImagePicker = false
}
override fun imagePickerControllerDidCancel(picker: UIImagePickerController) {
showImagePicker = false
}
}
}
},
modifier = modifier
)
}
}Integrate Material Design components with iOS navigation patterns.
/**
* iOS-specific navigation utilities for Material components
*/
object iOSNavigation {
/**
* Push a new screen onto the iOS navigation stack
*/
expect fun pushViewController(viewController: UIViewController, animated: Boolean = true)
/**
* Pop the current screen from the iOS navigation stack
*/
expect fun popViewController(animated: Boolean = true)
/**
* Present a modal screen
*/
expect fun presentViewController(
viewController: UIViewController,
animated: Boolean = true,
completion: (() -> Unit)? = null
)
/**
* Dismiss a modal screen
*/
expect fun dismissViewController(
animated: Boolean = true,
completion: (() -> Unit)? = null
)
}Usage Examples:
// Material components with iOS navigation
@Composable
fun MaterialScreenWithiOSNavigation() {
Scaffold(
topBar = {
TopAppBar(
title = { Text("Material + iOS") },
navigationIcon = {
IconButton(onClick = {
// Use iOS navigation
iOSNavigation.popViewController()
}) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
}
},
actions = {
IconButton(onClick = {
// Present iOS modal
val settingsVC = SettingsViewController()
iOSNavigation.presentViewController(settingsVC)
}) {
Icon(Icons.Default.Settings, contentDescription = "Settings")
}
}
)
}
) { paddingValues ->
LazyColumn(
modifier = Modifier.padding(paddingValues),
contentPadding = PaddingValues(16.dp)
) {
items(dataItems) { item ->
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp)
.clickable {
// Navigate to detail screen
val detailVC = DetailViewController(item)
iOSNavigation.pushViewController(detailVC)
}
) {
// Card content
}
}
}
}
}Handle iOS safe areas and layout constraints within Material components.
/**
* iOS safe area insets integration
*/
@Composable
expect fun WindowInsets.Companion.safeArea(): WindowInsets
/**
* Apply iOS safe area padding to Material components
*/
@Composable
fun Modifier.iOSSafeArea(): Modifier = this.then(
windowInsetsPadding(WindowInsets.safeArea())
)
/**
* iOS status bar integration
*/
object iOSStatusBar {
expect fun setStatusBarStyle(style: StatusBarStyle)
expect fun setStatusBarHidden(hidden: Boolean, animated: Boolean = true)
}
enum class StatusBarStyle {
Default, LightContent, DarkContent
}Usage Examples:
// Material app with iOS safe area handling
@Composable
fun iOSMaterialApp() {
MaterialTheme(
colors = if (isSystemInDarkTheme()) darkColors() else lightColors()
) {
// Update status bar based on theme
LaunchedEffect(MaterialTheme.colors.isLight) {
iOSStatusBar.setStatusBarStyle(
if (MaterialTheme.colors.isLight) StatusBarStyle.DarkContent
else StatusBarStyle.LightContent
)
}
Scaffold(
modifier = Modifier.iOSSafeArea(),
topBar = {
TopAppBar(
title = { Text("iOS Safe Area") },
backgroundColor = Color.Transparent,
elevation = 0.dp
)
}
) { paddingValues ->
// Content automatically respects safe areas
Content(modifier = Modifier.padding(paddingValues))
}
}
}
// Handle keyboard avoidance
@Composable
fun KeyboardAwareContent() {
val keyboardHeight by keyboardAsState()
Column(
modifier = Modifier
.fillMaxSize()
.padding(bottom = keyboardHeight)
) {
// Content that moves up when keyboard appears
TextField(
value = text,
onValueChange = { text = it },
modifier = Modifier.fillMaxWidth()
)
}
}
@Composable
expect fun keyboardAsState(): State<Dp>Integrate iOS haptic feedback with Material component interactions.
/**
* iOS haptic feedback integration
*/
object iOSHaptics {
/**
* Trigger light impact haptic feedback
*/
expect fun lightImpact()
/**
* Trigger medium impact haptic feedback
*/
expect fun mediumImpact()
/**
* Trigger heavy impact haptic feedback
*/
expect fun heavyImpact()
/**
* Trigger selection haptic feedback
*/
expect fun selectionChanged()
/**
* Trigger notification haptic feedback
*/
expect fun notification(type: NotificationHapticType)
}
enum class NotificationHapticType {
Success, Warning, Error
}Usage Examples:
// Material buttons with iOS haptic feedback
@Composable
fun HapticButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
content: @Composable RowScope.() -> Unit
) {
Button(
onClick = {
iOSHaptics.lightImpact()
onClick()
},
modifier = modifier,
content = content
)
}
// Switch with haptic feedback
@Composable
fun HapticSwitch(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier
) {
Switch(
checked = checked,
onCheckedChange = { newValue ->
iOSHaptics.selectionChanged()
onCheckedChange(newValue)
},
modifier = modifier
)
}
// Slider with haptic steps
@Composable
fun HapticSlider(
value: Float,
onValueChange: (Float) -> Unit,
steps: Int = 0,
modifier: Modifier = Modifier
) {
var lastStepValue by remember { mutableStateOf(value) }
Slider(
value = value,
onValueChange = { newValue ->
if (steps > 0) {
val stepValue = (newValue * steps).roundToInt() / steps.toFloat()
if (stepValue != lastStepValue) {
iOSHaptics.selectionChanged()
lastStepValue = stepValue
}
}
onValueChange(newValue)
},
steps = steps,
modifier = modifier
)
}Present native iOS alerts while maintaining Material theming consistency.
/**
* Present native iOS alert dialogs
*/
object iOSAlerts {
/**
* Show a native iOS alert
*/
expect fun showAlert(
title: String,
message: String? = null,
primaryButton: AlertButton,
secondaryButton: AlertButton? = null
)
/**
* Show a native iOS action sheet
*/
expect fun showActionSheet(
title: String? = null,
message: String? = null,
actions: List<AlertAction>,
anchor: CGRect? = null
)
}
data class AlertButton(
val title: String,
val style: AlertButtonStyle = AlertButtonStyle.Default,
val action: () -> Unit
)
data class AlertAction(
val title: String,
val style: AlertActionStyle = AlertActionStyle.Default,
val action: () -> Unit
)
enum class AlertButtonStyle {
Default, Cancel, Destructive
}
enum class AlertActionStyle {
Default, Cancel, Destructive
}Usage Examples:
// Material components triggering iOS alerts
@Composable
fun MaterialWithiOSAlerts() {
var showDeleteDialog by remember { mutableStateOf(false) }
LazyColumn {
items(items) { item ->
Card(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Row(
modifier = Modifier.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(item.title)
IconButton(onClick = {
// Show native iOS alert
iOSAlerts.showAlert(
title = "Delete Item",
message = "Are you sure you want to delete '${item.title}'?",
primaryButton = AlertButton(
title = "Delete",
style = AlertButtonStyle.Destructive
) {
deleteItem(item)
},
secondaryButton = AlertButton(
title = "Cancel",
style = AlertButtonStyle.Cancel
) {
// Do nothing
}
)
}) {
Icon(
Icons.Default.Delete,
contentDescription = "Delete",
tint = MaterialTheme.colors.error
)
}
}
}
}
}
}
// Action sheet from Material FAB
@Composable
fun FabWithActionSheet() {
FloatingActionButton(
onClick = {
iOSAlerts.showActionSheet(
title = "Choose Action",
actions = listOf(
AlertAction("Take Photo") { /* camera action */ },
AlertAction("Choose from Library") { /* gallery action */ },
AlertAction("Cancel", AlertActionStyle.Cancel) { }
)
)
}
) {
Icon(Icons.Default.Add, contentDescription = "Add")
}
}/**
* iOS system integration utilities
*/
object iOSSystem {
/**
* Get current iOS version
*/
expect val systemVersion: String
/**
* Check if device supports specific features
*/
expect fun supportsHaptics(): Boolean
expect fun supportsCamera(): Boolean
expect fun supportsFaceID(): Boolean
expect fun supportsTouchID(): Boolean
/**
* Device information
*/
expect val deviceModel: String
expect val isIPad: Boolean
expect val isIPhone: Boolean
/**
* Open iOS system settings
*/
expect fun openSettings()
/**
* Share content using iOS share sheet
*/
expect fun share(
items: List<Any>,
excludedActivityTypes: List<String> = emptyList()
)
}Install with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-compose-material--material-uikitarm64