Flutter Cupertino Tutorial: How to Build an iOS App That Looks and Feels Native

杭州程式設計師張張發表於2022-11-24

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 and tabBuilder 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 named BottomNavigationBarItem . 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. Each CupertinoTabView 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 , return CustomScrollView
  • Step 2: In CustomScrollView , add the CupertinoSliverNavigationBar widget. This widget hides the navigation bar when scrolling.
  • Step 3: Inside CupertinoSliverNavigationBar , add the largeTitle 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 CupertinoActivityIndi​​cator widget. This widget displays an iOS-style activity indicator that rotates clockwise. Let's use ---5e7e3fef50663136b40f1fc21242a58b--- with the Text CupertinoActivityIndi​​cator to display the "waiting for network" indication.

step

  • Step 1: Inside CupertinoSliverNavigationBar , add the intermediate parameter and assign the Row widget.
  • Step 2: In the Row widget, add CupertinoActivityIndi​​cator
  • 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, call showCupertinoModalPopup .
  • Step 3: In the builder for showCupertinoModalPopup , return the CupertinoActionSheet .
  • Step 4: In CupertinoActionSheet use the CupertinoActionSheetAction 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, call showCupertinoDialog .
  • Step 3: Return the CupertinoAlertDialog in the builder of showCupertinoDialog .
  • Step 4: In CupertinoAlertDialog use the CupertinoDialogAction 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 and use24hFormat .
  • 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

相關文章