Building a Native-Looking iOS App with Flutter
When building a mobile app, it’s essential to create a user interface that looks and feels native to the platform. For iOS apps, this means using a design system that incorporates elements such as navigation bars, tab bars, and action sheets. In this tutorial, we’ll explore how to build an iOS app that looks and feels native using Flutter.
Creating a Simple Page
To get started, let’s create a simple page that displays a title at the top and a “Hello” message in the center. We can use the CupertinoApp
widget to create a basic iOS-style app.
“`dart
import ‘package:flutter/material.dart’;
import ‘package:flutter/cupertino.dart’;
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CupertinoApp(
home: CupertinoPageScaffold(
child: Center(
child: Text(‘Hello’),
),
),
);
}
}
“`
Adding Tabs
Next, let’s add three tabs to our app: Calls, Chats, and Settings. We can use the CupertinoTabScaffold
widget to create a tab bar at the bottom of the screen.
“`dart
import ‘package:flutter/material.dart’;
import ‘package:flutter/cupertino.dart’;
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CupertinoApp(
home: CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: [
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.phone),
label: ‘Calls’,
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.chat_bubble),
label: ‘Chats’,
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.settings),
label: ‘Settings’,
),
],
),
tabBuilder: (context, index) {
return CupertinoTabView(
builder: (context) {
return Center(
child: Text(‘Tab $index’),
);
},
);
},
),
);
}
}
“`
Adding a Navigation Bar
Now, let’s add a navigation bar to our app that hides when the user scrolls down. We can use the CupertinoSliverNavigationBar
widget to create a navigation bar that hides when the user scrolls.
“`dart
import ‘package:flutter/material.dart’;
import ‘package:flutter/cupertino.dart’;
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CupertinoApp(
home: CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: [
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.phone),
label: ‘Calls’,
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.chat_bubble),
label: ‘Chats’,
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.settings),
label: ‘Settings’,
),
],
),
tabBuilder: (context, index) {
return CupertinoTabView(
builder: (context) {
return CustomScrollView(
slivers: [
CupertinoSliverNavigationBar(
largeTitle: Text(‘Navigation Bar’),
),
SliverList(
delegate: SliverChildListDelegate(
[
Container(
height: 100,
color: Colors.red,
),
Container(
height: 100,
color: Colors.blue,
),
],
),
),
],
);
},
);
},
),
);
}
}
“`
Adding a Loading Indicator
Next, let’s add a loading indicator to our app that displays when the user is waiting for data to load. We can use the CupertinoActivityIndicator
widget to create a loading indicator.
“`dart
import ‘package:flutter/material.dart’;
import ‘package:flutter/cupertino.dart’;
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CupertinoApp(
home: CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: [
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.phone),
label: ‘Calls’,
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.chat_bubble),
label: ‘Chats’,
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.settings),
label: ‘Settings’,
),
],
),
tabBuilder: (context, index) {
return CupertinoTabView(
builder: (context) {
return Center(
child: CupertinoActivityIndicator(),
);
},
);
},
),
);
}
}
“`
Enabling Search
Now, let’s enable search in our app. We can use the CupertinoSearchTextField
widget to create a search field.
“`dart
import ‘package:flutter/material.dart’;
import ‘package:flutter/cupertino.dart’;
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CupertinoApp(
home: CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: [
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.phone),
label: ‘Calls’,
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.chat_bubble),
label: ‘Chats’,
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.settings),
label: ‘Settings’,
),
],
),
tabBuilder: (context, index) {
return CupertinoTabView(
builder: (context) {
return CustomScrollView(
slivers: [
CupertinoSliverNavigationBar(
largeTitle: Text(‘Search’),
),
SliverList(
delegate: SliverChildListDelegate(
[
CupertinoSearchTextField(),
],
),
),
],
);
},
);
},
),
);
}
}
“`
Adding a Switch
Next, let’s add a switch to our app. We can use the CupertinoSwitch
widget to create a switch.
“`dart
import ‘package:flutter/material.dart’;
import ‘package:flutter/cupertino.dart’;
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CupertinoApp(
home: CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: [
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.phone),
label: ‘Calls’,
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.chat_bubble),
label: ‘Chats’,
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.settings),
label: ‘Settings’,
),
],
),
tabBuilder: (context, index) {
return CupertinoTabView(
builder: (context) {
return Center(
child: CupertinoSwitch(
value: true,
onChanged: (value) {},
),
);
},
);
},
),
);
}
}
“`
Showing an Action Sheet
Now, let’s show an action sheet in our app. We can use the CupertinoActionSheet
widget to create an action sheet.
“`dart
import ‘package:flutter/material.dart’;
import ‘package:flutter/cupertino.dart’;
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CupertinoApp(
home: CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: [
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.phone),
label: ‘Calls’,
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.chat_bubble),
label: ‘Chats’,
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.settings),
label: ‘Settings’,
),
],
),
tabBuilder: (context, index) {
return CupertinoTabView(
builder: (context) {
return Center(
child: CupertinoButton(
onPressed: () {
showCupertinoModalPopup(
context: context,
builder: (context) {
return CupertinoActionSheet(
actions: [
CupertinoActionSheetAction(
onPressed: () {},
child: Text(‘Action 1’),
),
CupertinoActionSheetAction(
onPressed: () {},
child: Text(‘Action 2’),
),
],
);
},
);
},
child: Text(‘Show Action Sheet’),
),
);
},
);
},
),
);
}
}
“`
Displaying an Alert Dialog
Next, let’s display an alert dialog in our app. We can use the CupertinoAlertDialog
widget to create an alert dialog.
“`dart
import ‘package:flutter/material.dart’;
import ‘package:flutter/cupertino.dart’;
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CupertinoApp(
home: CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: [
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.phone),
label: ‘Calls’,
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.chat_bubble),
label: ‘Chats’,
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.settings),
label: ‘Settings’,
),
],
),
tabBuilder: (context, index) {
return CupertinoTabView(
builder: (context) {
return Center(
child: CupertinoButton(
onPressed: () {
showCupertinoDialog(
context: context,
builder: (context) {
return CupertinoAlertDialog(
title: Text(‘Alert Dialog’),
content: Text(‘This is an alert dialog’),
actions: [