All front-end applications use some kind of design system to help users accomplish tasks more easily. They may use an in-house developed custom design system or an established design system such as Material Design or Cupertino (iOS).
Material Design is developed by Google and can be used to develop Android, iOS, web and desktop applications.
Cupertino is developed by Apple. It is based on Apple's Human Interface Guidelines, which implement the current iOS design language.
The Flutter SDK comes with Material and Cupertino widget libraries for developing an application that looks and feels suitable for either platform.
You can still build an application using just the Material widgets library. However, if you want to build an app that looks like standard iOS style, you should strongly consider using the Cupertino library.
In this tutorial, we'll build a simple app with three tabs at the bottom; call , chat , and settings .
On the Calls tab we will add a simple navigation bar; the Chats tab will display a list of members and allow the end user to search for any member; and on the Settings tab we will use various Cupertino style widgets to build the settings page.
Here's what the final application looks like:
1. Create a simple page
Let's start by creating a simple page that displays the page title at the top and the "Hello" message in the middle. To build such a page, you have to delete all content of the newly created project and replace it with the following code:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'simple_page.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]).then((value) => runApp(MyApp()));
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
// 1 <-- SEE HERE
return CupertinoApp(
// 2 <-- SEE HERE
theme: CupertinoThemeData(brightness: Brightness.light),
home: CupertinoSimpleHomePage(),
);
}
}
class CupertinoSimpleHomePage extends StatefulWidget {
const CupertinoSimpleHomePage({Key? key}) : super(key: key);
@override
_CupertinoSimpleHomePageState createState() =>
_CupertinoSimpleHomePageState();
}
class _CupertinoSimpleHomePageState extends State<CupertinoSimpleHomePage> {
@override
Widget build(BuildContext context) {
// 3 <-- SEE HERE
return const CupertinoPageScaffold(
// 4 <-- SEE HERE
navigationBar: CupertinoNavigationBar(
middle: Text('Chat App'),
),
child: Center(
child: Text('Hi'),
),
);
}
}
Code description:
- CupertinoApp : The CupertinoApp widget allows you to add widgets that are primarily used to build an iOS-style app.
- CupertinoThemeData : With this widget you can specify the styles applied
- CupertinoPageScaffold : CupertinoPageScaffold helps to build the layout of the page, such as adding a navigation bar
- CupertinoNavigationBar : This widget creates a navigation bar that looks like a native iOS style.
output
2. Add tabs
These tabs are used to support the main navigation of the application. Let's add three tabs at the bottom, each with a different name and icon. To create the label, we have to replace CupertinoPageScaffold
with CupertinoTabScaffold
.
// 1 <-- SEE HERE
return CupertinoTabScaffold(
// 2 <-- SEE HERE
tabBar: CupertinoTabBar(
currentIndex: 1,
items: const <BottomNavigationBarItem>[
// 3 <-- SEE HERE
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.phone), label: 'Calls'),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.chat_bubble_2), label: 'Chats'),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.settings), label: 'Settings'),
],
),
tabBuilder: (context, index) {
late final CupertinoTabView returnValue;
switch (index) {
case 0:
// 4 <-- SEE HERE
returnValue = CupertinoTabView(builder: (context) {
return const CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Calls'),
),
child: Center(child: Text('Calls')));
});
break;
case 1:
returnValue = CupertinoTabView(
builder: (context) {
return CupertinoChatPage();
},
);
break;
case 2:
returnValue = CupertinoTabView(
builder: (context) {
return CupertinoSettingsPage();
},
);
break;
}
return returnValue;
},
);
- CupertinoTabScaffold : The CupertinoTabScaffold widget contains parameters such as
tabBar
andtabBuilder
that allow you to create tab bar items and tab bar views. -
CupertinoTabBar
:CupertinoTabBar
The widget adds a tab bar at the bottom of the screen. It displays multiple items using a widget namedBottomNavigationBarItem
.currentIndex
Properties allow you to control the active tab when the application starts -
BottomNavigationBarItem
: This widget displays an item on the tab bar. It contains useful parameters like icon, label and background color to build an item. -
CupertinoTabView
:CupertinoTabView
The widget is responsible for populating the selected tab with content. EachCupertinoTabView
has its own navigation stack.
output
3. Add a NavigationBar that is hidden when scrolling
In the previous steps, we've built a basic setup from which we can start adding more widgets.
In the current example, the basic navbar is always on top when scrolling down the list. We can improve the user experience by hiding the navigation bar when the user starts scrolling.
step
- Step 1: Inside
CupertinoTabView
, returnCustomScrollView
- Step 2: In
CustomScrollView
, add the CupertinoSliverNavigationBar widget. This widget hides the navigation bar when scrolling. - Step 3: Inside
CupertinoSliverNavigationBar
, add thelargeTitle
parameter to display the navigation title.
code
CupertinoTabView(
builder: (context) {
return CustomScrollView(
slivers: <Widget>[
CupertinoSliverNavigationBar(
largeTitle: Text('Chats'),
),
],
);
},
);
output
4. Show loading indicator
To show the loading indicator you can use the CupertinoActivityIndicator
widget. This widget displays an iOS-style activity indicator that rotates clockwise. Let's use ---5e7e3fef50663136b40f1fc21242a58b--- with the Text
CupertinoActivityIndicator
to display the "waiting for network" indication.
step
- Step 1: Inside
CupertinoSliverNavigationBar
, add the intermediate parameter and assign theRow
widget. - Step 2: In the
Row
widget, addCupertinoActivityIndicator
- Step 3: Add one more widget (ie Text widget)
Code:
CupertinoSliverNavigationBar(
largeTitle: Text('Chats'),
leading: Text(
'Edit',
style: TextStyle(color: CupertinoColors.link),
),
middle: Row(
mainAxisSize: MainAxisSize.min,
children: const [
CupertinoActivityIndicator(),
SizedBox(width: 8),
Text('Waiting for network')
],
),
)
output
5. Start the search
Let's populate the Chat tab with some users and implement the search functionality.
To do this, we will:
- Create
users
model class - use it to populate some user data
- Display with custom list tile widget
- Use the
CupertinoSearchTextField
widget to enable search
step:
Step 1: Create a user list.
const List<User> users = const <User>[
const User('Jack', Colors.greenAccent),
const User('Lucy', Colors.green),
const User('Luna', Colors.black26),
const User('Oliver', Colors.blue),
const User('Lily', Colors.amberAccent),
const User('Milo', Colors.purple),
const User('Max', Colors.pink),
const User('Kitty', Colors.yellowAccent),
const User('Simba', Colors.red),
const User('Zoe', Colors.blueAccent),
const User('Jasper', Colors.deepOrange),
const User('Stella', Colors.cyan),
const User('Lola', Colors.lightBlue),
const User('Halsey', Colors.deepPurpleAccent),
const User('Taylor', Colors.indigoAccent),
];
Step 2: Copy all users into filteredUsers
.
List<User> _filteredUsers = users;
Step 3: Add SliverGrid
widget and use filteredUsers
to display a list of users in any scrollable view.
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 1,
childAspectRatio: 5,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return UserTile(_filteredUsers[index]);
},
childCount: _filteredUsers.length,
),
)
Step 4: Below CupertinoSliverNavigationBar
, add the ---5d60135073b8a72e88---cf0 widget with FractionallySizedBox
and ClipRect
SliverToBoxAdapter
Step 5: Add the CupertinoSearchTextField
widget as a child widget. CupertinoSearchTextField
The widget is similar to the normal Textfield
widget, but also mimics the iOS-style appearance and behavior.
SliverToBoxAdapter(
child: FractionallySizedBox(
widthFactor: 0.9,
child: ClipRect(
child: Padding(
padding: const EdgeInsets.only(top: 16),
child: CupertinoSearchTextField(
controller: _controller,
onChanged: (value) {
_updateUserList(value);
},
onSubmitted: (value) {
_updateUserList(value);
},
onSuffixTap: () {
_updateUserList('');
},
),
)),
),
)
Step 6: Add the _updateUsersList()
method to find users matching the search term.
void _updateUserList(String value) {
debugPrint('$value');
if (value.length > 0) {
_filteredUsers = _filteredUsers
.where((element) =>
element.name.toLowerCase().contains(value.toLowerCase()))
.toList();
} else {
_controller.text = '';
_filteredUsers = users;
}
setState(() {});
}
output
6. Add switch switch
Using the CupertinoSwitch widget, you can create iOS style switches in your application. Let's add the CupertinoSwitch
widget in the Settings tab.
code
CupertinoFormSection(
header: Text('Account Details'),
children: [
CupertinoFormRow(
prefix: Text('Chat Backup'),
child: CupertinoSwitch(
value: chatBackup,
onChanged: (value) {
setState(() {
chatBackup = !chatBackup;
});
},
),
),
],
),
output
7. Display ActionSheet
To display ActionSheet
, you can use the CupertinoActionSheet
widget. This widget is used to allow the user to choose between multiple entries.
step:
- Step 1: Add the
CupertinoButton
widget. - Step 2: In the
onPressed
method, callshowCupertinoModalPopup
. - Step 3: In the builder for
showCupertinoModalPopup
, return the CupertinoActionSheet . - Step 4: In
CupertinoActionSheet
use theCupertinoActionSheetAction
widget to return some operations.
code
Center(
child: CupertinoButton(
onPressed: () {
showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) => CupertinoActionSheet(
title: const Text('Set Wallpaper Theme'),
actions: <CupertinoActionSheetAction>[
CupertinoActionSheetAction(
child: const Text('Dark'),
onPressed: () {
Navigator.pop(context);
},
),
CupertinoActionSheetAction(
child: const Text('Light'),
onPressed: () {
Navigator.pop(context);
},
)
],
),
);
},
child: const Text('Chat Wallpaper'),
),
)
output
8. Show AlertDialog
To display AlertDialog
, you can use the CupertinoAlertDialog
widget. CupertinoAlertDialog
The widget is used to confirm the user's actions - for example, when deleting an account.
step:
- Step 1: Add the
CupertinoButton
widget. - Step 2: In the
onPressed
method, callshowCupertinoDialog
. - Step 3: Return the CupertinoAlertDialog in the builder of
showCupertinoDialog
. - Step 4: In
CupertinoAlertDialog
use theCupertinoDialogAction
widget to return some actions.
Code:
Center(
child: CupertinoButton(
onPressed: () {
showCupertinoDialog<void>(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: const Text('Delete chat'),
content: const Text('Proceed with deleting chat?'),
actions: <CupertinoDialogAction>[
CupertinoDialogAction(
child: const Text('No'),
onPressed: () {
Navigator.pop(context);
},
),
CupertinoDialogAction(
child: const Text('Yes'),
isDestructiveAction: true,
onPressed: () {
// Do something destructive.
},
)
],
),
);
},
child: const Text('Delete all chat'),
),
)
output
9. Add CupertinoDatePicker
CupertinoDatePicker
The widget allows the user to pick dates in standard iOS style.
step:
- Step 1: Add the
CupertinoButton
widget. - Step 2: In the
onPressed
method, call_showDialog
. - Step 3: Return a CupertinoDatePicker widget with some useful parameters such as
initialDateTime
,mode
anduse24hFormat
. - Step 4: Add the
onDateTimeChanged
attribute and rebuild the widget with the new date.
code
Center(
child: CupertinoButton(
// Display a CupertinoDatePicker in date picker mode.
onPressed: () => _showDialog(
CupertinoDatePicker(
backgroundColor: CupertinoColors.white,
initialDateTime: date,
mode: CupertinoDatePickerMode.date,
use24hFormat: true,
// This is called when the user changes the date.
onDateTimeChanged: (DateTime newDate) {
setState(() => date = newDate);
},
),
),
// In this example, the date value is formatted manually. You can use intl package
// to format the value based on user's locale settings.
child: Text(
'${date.month}-${date.day}-${date.year}',
style: const TextStyle(
fontSize: 22.0,
),
),
),
)
output
You can find the full source code here.
Source code: https://github.com/pinkeshdarji/cupertino_app