or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-components.mdicons.mdindex.mdinput-components.mdios-integration.mdnavigation.mdtheming.md

ios-integration.mddocs/

0

# iOS Platform Integration

1

2

Seamless integration with native iOS UIKit components and platform-specific behaviors, enabling Material Design components to work harmoniously with iOS native development patterns.

3

4

## Capabilities

5

6

### UIKit View Integration

7

8

Embed native iOS UIKit views directly within Compose Material layouts.

9

10

```kotlin { .api }

11

/**

12

* Composes a UIView provided by `factory` into a Compose UI hierarchy

13

* @param factory A function which is called to create the UIView

14

* @param modifier Modifier to be applied to this UIKitView

15

* @param update A lambda which is called whenever the UIKitView is recomposed

16

* @param onResize Called when the UIView's size changes

17

* @param onRelease Called when the UIKitView is removed from the composition

18

*/

19

@Composable

20

fun <T : UIView> UIKitView(

21

factory: () -> T,

22

modifier: Modifier = Modifier,

23

update: (T) -> Unit = {},

24

onResize: ((T, CGRect) -> Unit)? = null,

25

onRelease: (T) -> Unit = {}

26

)

27

```

28

29

**Usage Examples:**

30

31

```kotlin

32

// Embed a native iOS text field

33

@Composable

34

fun NativeTextField(

35

text: String,

36

onTextChange: (String) -> Unit,

37

placeholder: String = ""

38

) {

39

UIKitView(

40

factory = {

41

UITextField().apply {

42

this.placeholder = placeholder

43

borderStyle = UITextBorderStyle.UITextBorderStyleRoundedRect

44

45

// Set up text change listener

46

addTarget(

47

target = object : NSObject() {

48

@ObjCAction

49

fun textChanged() {

50

onTextChange(text ?: "")

51

}

52

},

53

action = NSSelectorFromString("textChanged"),

54

forControlEvents = UIControlEvents.UIControlEventEditingChanged

55

)

56

}

57

},

58

update = { textField ->

59

textField.text = text

60

},

61

modifier = Modifier

62

.fillMaxWidth()

63

.height(44.dp)

64

)

65

}

66

67

// Embed a native iOS map view

68

@Composable

69

fun NativeMapView(

70

latitude: Double,

71

longitude: Double,

72

modifier: Modifier = Modifier

73

) {

74

UIKitView(

75

factory = {

76

MKMapView().apply {

77

showsUserLocation = true

78

userInteractionEnabled = true

79

}

80

},

81

update = { mapView ->

82

val coordinate = CLLocationCoordinate2DMake(latitude, longitude)

83

val region = MKCoordinateRegionMake(

84

coordinate,

85

MKCoordinateSpanMake(0.01, 0.01)

86

)

87

mapView.setRegion(region, animated = true)

88

},

89

modifier = modifier

90

)

91

}

92

93

// Embed iOS camera preview

94

@Composable

95

fun CameraPreview(

96

modifier: Modifier = Modifier,

97

onCapturePhoto: () -> Unit = {}

98

) {

99

UIKitView(

100

factory = {

101

UIView().apply {

102

backgroundColor = UIColor.blackColor

103

// Set up AVCaptureSession here

104

}

105

},

106

modifier = modifier

107

)

108

}

109

```

110

111

### UIKit View Controller Integration

112

113

Embed complete UIKit view controllers within Compose layouts.

114

115

```kotlin { .api }

116

/**

117

* Composes a UIViewController provided by `factory` into a Compose UI hierarchy

118

* @param factory A function which is called to create the UIViewController

119

* @param modifier Modifier to be applied to this UIKitViewController

120

* @param update A lambda which is called whenever the UIKitViewController is recomposed

121

* @param onRelease Called when the UIKitViewController is removed from the composition

122

*/

123

@Composable

124

fun <T : UIViewController> UIKitViewController(

125

factory: () -> T,

126

modifier: Modifier = Modifier,

127

update: (T) -> Unit = {},

128

onRelease: (T) -> Unit = {}

129

)

130

```

131

132

**Usage Examples:**

133

134

```kotlin

135

// Embed iOS document picker

136

@Composable

137

fun DocumentPicker(

138

onDocumentSelected: (URL) -> Unit,

139

modifier: Modifier = Modifier

140

) {

141

var showPicker by remember { mutableStateOf(false) }

142

143

if (showPicker) {

144

UIKitViewController(

145

factory = {

146

UIDocumentPickerViewController(

147

documentTypes = listOf("public.data"),

148

mode = UIDocumentPickerMode.UIDocumentPickerModeImport

149

).apply {

150

delegate = object : UIDocumentPickerDelegate {

151

override fun documentPicker(

152

controller: UIDocumentPickerViewController,

153

didPickDocumentsAt: List<URL>

154

) {

155

didPickDocumentsAt.firstOrNull()?.let { url ->

156

onDocumentSelected(url)

157

}

158

showPicker = false

159

}

160

161

override fun documentPickerWasCancelled(

162

controller: UIDocumentPickerViewController

163

) {

164

showPicker = false

165

}

166

}

167

}

168

},

169

modifier = modifier

170

)

171

}

172

173

// Trigger button

174

Button(onClick = { showPicker = true }) {

175

Text("Select Document")

176

}

177

}

178

179

// Embed iOS image picker

180

@Composable

181

fun ImagePicker(

182

onImageSelected: (UIImage) -> Unit,

183

modifier: Modifier = Modifier

184

) {

185

var showImagePicker by remember { mutableStateOf(false) }

186

187

if (showImagePicker) {

188

UIKitViewController(

189

factory = {

190

UIImagePickerController().apply {

191

sourceType = UIImagePickerControllerSourceType.UIImagePickerControllerSourceTypePhotoLibrary

192

delegate = object : UIImagePickerControllerDelegate, UINavigationControllerDelegate {

193

override fun imagePickerController(

194

picker: UIImagePickerController,

195

didFinishPickingMediaWithInfo: Map<Any?, Any?>

196

) {

197

val image = didFinishPickingMediaWithInfo[UIImagePickerControllerOriginalImage] as? UIImage

198

image?.let { onImageSelected(it) }

199

showImagePicker = false

200

}

201

202

override fun imagePickerControllerDidCancel(picker: UIImagePickerController) {

203

showImagePicker = false

204

}

205

}

206

}

207

},

208

modifier = modifier

209

)

210

}

211

}

212

```

213

214

### Native iOS Navigation Integration

215

216

Integrate Material Design components with iOS navigation patterns.

217

218

```kotlin { .api }

219

/**

220

* iOS-specific navigation utilities for Material components

221

*/

222

object iOSNavigation {

223

/**

224

* Push a new screen onto the iOS navigation stack

225

*/

226

expect fun pushViewController(viewController: UIViewController, animated: Boolean = true)

227

228

/**

229

* Pop the current screen from the iOS navigation stack

230

*/

231

expect fun popViewController(animated: Boolean = true)

232

233

/**

234

* Present a modal screen

235

*/

236

expect fun presentViewController(

237

viewController: UIViewController,

238

animated: Boolean = true,

239

completion: (() -> Unit)? = null

240

)

241

242

/**

243

* Dismiss a modal screen

244

*/

245

expect fun dismissViewController(

246

animated: Boolean = true,

247

completion: (() -> Unit)? = null

248

)

249

}

250

```

251

252

**Usage Examples:**

253

254

```kotlin

255

// Material components with iOS navigation

256

@Composable

257

fun MaterialScreenWithiOSNavigation() {

258

Scaffold(

259

topBar = {

260

TopAppBar(

261

title = { Text("Material + iOS") },

262

navigationIcon = {

263

IconButton(onClick = {

264

// Use iOS navigation

265

iOSNavigation.popViewController()

266

}) {

267

Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")

268

}

269

},

270

actions = {

271

IconButton(onClick = {

272

// Present iOS modal

273

val settingsVC = SettingsViewController()

274

iOSNavigation.presentViewController(settingsVC)

275

}) {

276

Icon(Icons.Default.Settings, contentDescription = "Settings")

277

}

278

}

279

)

280

}

281

) { paddingValues ->

282

LazyColumn(

283

modifier = Modifier.padding(paddingValues),

284

contentPadding = PaddingValues(16.dp)

285

) {

286

items(dataItems) { item ->

287

Card(

288

modifier = Modifier

289

.fillMaxWidth()

290

.padding(vertical = 4.dp)

291

.clickable {

292

// Navigate to detail screen

293

val detailVC = DetailViewController(item)

294

iOSNavigation.pushViewController(detailVC)

295

}

296

) {

297

// Card content

298

}

299

}

300

}

301

}

302

}

303

```

304

305

### Safe Area and Layout Integration

306

307

Handle iOS safe areas and layout constraints within Material components.

308

309

```kotlin { .api }

310

/**

311

* iOS safe area insets integration

312

*/

313

@Composable

314

expect fun WindowInsets.Companion.safeArea(): WindowInsets

315

316

/**

317

* Apply iOS safe area padding to Material components

318

*/

319

@Composable

320

fun Modifier.iOSSafeArea(): Modifier = this.then(

321

windowInsetsPadding(WindowInsets.safeArea())

322

)

323

324

/**

325

* iOS status bar integration

326

*/

327

object iOSStatusBar {

328

expect fun setStatusBarStyle(style: StatusBarStyle)

329

expect fun setStatusBarHidden(hidden: Boolean, animated: Boolean = true)

330

}

331

332

enum class StatusBarStyle {

333

Default, LightContent, DarkContent

334

}

335

```

336

337

**Usage Examples:**

338

339

```kotlin

340

// Material app with iOS safe area handling

341

@Composable

342

fun iOSMaterialApp() {

343

MaterialTheme(

344

colors = if (isSystemInDarkTheme()) darkColors() else lightColors()

345

) {

346

// Update status bar based on theme

347

LaunchedEffect(MaterialTheme.colors.isLight) {

348

iOSStatusBar.setStatusBarStyle(

349

if (MaterialTheme.colors.isLight) StatusBarStyle.DarkContent

350

else StatusBarStyle.LightContent

351

)

352

}

353

354

Scaffold(

355

modifier = Modifier.iOSSafeArea(),

356

topBar = {

357

TopAppBar(

358

title = { Text("iOS Safe Area") },

359

backgroundColor = Color.Transparent,

360

elevation = 0.dp

361

)

362

}

363

) { paddingValues ->

364

// Content automatically respects safe areas

365

Content(modifier = Modifier.padding(paddingValues))

366

}

367

}

368

}

369

370

// Handle keyboard avoidance

371

@Composable

372

fun KeyboardAwareContent() {

373

val keyboardHeight by keyboardAsState()

374

375

Column(

376

modifier = Modifier

377

.fillMaxSize()

378

.padding(bottom = keyboardHeight)

379

) {

380

// Content that moves up when keyboard appears

381

TextField(

382

value = text,

383

onValueChange = { text = it },

384

modifier = Modifier.fillMaxWidth()

385

)

386

}

387

}

388

389

@Composable

390

expect fun keyboardAsState(): State<Dp>

391

```

392

393

### Haptic Feedback Integration

394

395

Integrate iOS haptic feedback with Material component interactions.

396

397

```kotlin { .api }

398

/**

399

* iOS haptic feedback integration

400

*/

401

object iOSHaptics {

402

/**

403

* Trigger light impact haptic feedback

404

*/

405

expect fun lightImpact()

406

407

/**

408

* Trigger medium impact haptic feedback

409

*/

410

expect fun mediumImpact()

411

412

/**

413

* Trigger heavy impact haptic feedback

414

*/

415

expect fun heavyImpact()

416

417

/**

418

* Trigger selection haptic feedback

419

*/

420

expect fun selectionChanged()

421

422

/**

423

* Trigger notification haptic feedback

424

*/

425

expect fun notification(type: NotificationHapticType)

426

}

427

428

enum class NotificationHapticType {

429

Success, Warning, Error

430

}

431

```

432

433

**Usage Examples:**

434

435

```kotlin

436

// Material buttons with iOS haptic feedback

437

@Composable

438

fun HapticButton(

439

onClick: () -> Unit,

440

modifier: Modifier = Modifier,

441

content: @Composable RowScope.() -> Unit

442

) {

443

Button(

444

onClick = {

445

iOSHaptics.lightImpact()

446

onClick()

447

},

448

modifier = modifier,

449

content = content

450

)

451

}

452

453

// Switch with haptic feedback

454

@Composable

455

fun HapticSwitch(

456

checked: Boolean,

457

onCheckedChange: (Boolean) -> Unit,

458

modifier: Modifier = Modifier

459

) {

460

Switch(

461

checked = checked,

462

onCheckedChange = { newValue ->

463

iOSHaptics.selectionChanged()

464

onCheckedChange(newValue)

465

},

466

modifier = modifier

467

)

468

}

469

470

// Slider with haptic steps

471

@Composable

472

fun HapticSlider(

473

value: Float,

474

onValueChange: (Float) -> Unit,

475

steps: Int = 0,

476

modifier: Modifier = Modifier

477

) {

478

var lastStepValue by remember { mutableStateOf(value) }

479

480

Slider(

481

value = value,

482

onValueChange = { newValue ->

483

if (steps > 0) {

484

val stepValue = (newValue * steps).roundToInt() / steps.toFloat()

485

if (stepValue != lastStepValue) {

486

iOSHaptics.selectionChanged()

487

lastStepValue = stepValue

488

}

489

}

490

onValueChange(newValue)

491

},

492

steps = steps,

493

modifier = modifier

494

)

495

}

496

```

497

498

### Native iOS Alerts and Dialogs

499

500

Present native iOS alerts while maintaining Material theming consistency.

501

502

```kotlin { .api }

503

/**

504

* Present native iOS alert dialogs

505

*/

506

object iOSAlerts {

507

/**

508

* Show a native iOS alert

509

*/

510

expect fun showAlert(

511

title: String,

512

message: String? = null,

513

primaryButton: AlertButton,

514

secondaryButton: AlertButton? = null

515

)

516

517

/**

518

* Show a native iOS action sheet

519

*/

520

expect fun showActionSheet(

521

title: String? = null,

522

message: String? = null,

523

actions: List<AlertAction>,

524

anchor: CGRect? = null

525

)

526

}

527

528

data class AlertButton(

529

val title: String,

530

val style: AlertButtonStyle = AlertButtonStyle.Default,

531

val action: () -> Unit

532

)

533

534

data class AlertAction(

535

val title: String,

536

val style: AlertActionStyle = AlertActionStyle.Default,

537

val action: () -> Unit

538

)

539

540

enum class AlertButtonStyle {

541

Default, Cancel, Destructive

542

}

543

544

enum class AlertActionStyle {

545

Default, Cancel, Destructive

546

}

547

```

548

549

**Usage Examples:**

550

551

```kotlin

552

// Material components triggering iOS alerts

553

@Composable

554

fun MaterialWithiOSAlerts() {

555

var showDeleteDialog by remember { mutableStateOf(false) }

556

557

LazyColumn {

558

items(items) { item ->

559

Card(

560

modifier = Modifier

561

.fillMaxWidth()

562

.padding(8.dp)

563

) {

564

Row(

565

modifier = Modifier.padding(16.dp),

566

horizontalArrangement = Arrangement.SpaceBetween,

567

verticalAlignment = Alignment.CenterVertically

568

) {

569

Text(item.title)

570

571

IconButton(onClick = {

572

// Show native iOS alert

573

iOSAlerts.showAlert(

574

title = "Delete Item",

575

message = "Are you sure you want to delete '${item.title}'?",

576

primaryButton = AlertButton(

577

title = "Delete",

578

style = AlertButtonStyle.Destructive

579

) {

580

deleteItem(item)

581

},

582

secondaryButton = AlertButton(

583

title = "Cancel",

584

style = AlertButtonStyle.Cancel

585

) {

586

// Do nothing

587

}

588

)

589

}) {

590

Icon(

591

Icons.Default.Delete,

592

contentDescription = "Delete",

593

tint = MaterialTheme.colors.error

594

)

595

}

596

}

597

}

598

}

599

}

600

}

601

602

// Action sheet from Material FAB

603

@Composable

604

fun FabWithActionSheet() {

605

FloatingActionButton(

606

onClick = {

607

iOSAlerts.showActionSheet(

608

title = "Choose Action",

609

actions = listOf(

610

AlertAction("Take Photo") { /* camera action */ },

611

AlertAction("Choose from Library") { /* gallery action */ },

612

AlertAction("Cancel", AlertActionStyle.Cancel) { }

613

)

614

)

615

}

616

) {

617

Icon(Icons.Default.Add, contentDescription = "Add")

618

}

619

}

620

```

621

622

## Platform-Specific Utilities

623

624

```kotlin { .api }

625

/**

626

* iOS system integration utilities

627

*/

628

object iOSSystem {

629

/**

630

* Get current iOS version

631

*/

632

expect val systemVersion: String

633

634

/**

635

* Check if device supports specific features

636

*/

637

expect fun supportsHaptics(): Boolean

638

expect fun supportsCamera(): Boolean

639

expect fun supportsFaceID(): Boolean

640

expect fun supportsTouchID(): Boolean

641

642

/**

643

* Device information

644

*/

645

expect val deviceModel: String

646

expect val isIPad: Boolean

647

expect val isIPhone: Boolean

648

649

/**

650

* Open iOS system settings

651

*/

652

expect fun openSettings()

653

654

/**

655

* Share content using iOS share sheet

656

*/

657

expect fun share(

658

items: List<Any>,

659

excludedActivityTypes: List<String> = emptyList()

660

)

661

}

662

```