ChatGPTを使ってCupertinoアプリの雛形を作ってみた (Flutter iOS音声メモアプリ開発 番外編)

音声メモアプリ開発のシリーズでは、アプリ公開を目標としていますが、UIデザインが今ひとつなのが気になってきました。他のiOSアプリを参考にCupertino Wigetsを使ってみたいと思いますが、よくわからないので、ChatGPTにお願いして雛形を作ってみることにしました。

CupertinoTabBar

TwitterやLINEを見ると、画面の最下部にメニューアイコンが並んでいます。これをCupertino widgetsで書いてもらいます。

こちらにCupertino (iOS-style) widgetsの一覧がありますので、CupertinoTabBarを使えば良いことがわかります。

そこで、ChatGPTに以下の様に入力します。

CupertinoTabBarを使ったアプリの雛形のコードを書いて

すると、以下のコードを出力してくれました。

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      title: 'Cupertino TabBar Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _selectedIndex = 0;

  static List<Widget> _widgetOptions = <Widget>[
    Text(
      'Index 0: Home',
    ),
    Text(
      'Index 1: Business',
    ),
    Text(
      'Index 2: School',
    ),
  ];

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: Text('Cupertino TabBar Demo'),
      ),
      child: SafeArea(
        child: Column(
          children: <Widget>[
            Expanded(
              child: _widgetOptions.elementAt(_selectedIndex),
            ),
            CupertinoTabBar(
              items: <BottomNavigationBarItem>[
                BottomNavigationBarItem(
                  icon: Icon(Icons.home),
                  title: Text('Home'),
                ),
                BottomNavigationBarItem(
                  icon: Icon(Icons.business),
                  title: Text('Business'),
                ),
                BottomNavigationBarItem(
                  icon: Icon(Icons.school),
                  title: Text('School'),
                ),
              ],
              currentIndex: _selectedIndex,
              onTap: _onItemTapped,
            ),
          ],
        ),
      ),
    );
  }
}

このコードでは、BottomNavigationBarItemのlabelがtitleになっているという間違いが一つありましたが、それを修正すると、動作させることができました。

画面下部の、Home, Bussiness, Schoolのアイコンを押すと、それぞれ画面の表示が切り替わります。

ただ、コードを読んでみると、同じ画面内の_widgetOptionsを_selectedIndexで切り替えているだけなので、複雑な画面が作りにくそうです。

そこで、以下の様にお願いしました。

タブを押したときに画面を遷移するように書き換えて

すると、以下のコードを書き出しました

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      title: 'Cupertino TabBar Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _selectedIndex = 0;

  static List<Widget> _widgetOptions = <Widget>[
    CupertinoTabView(
      builder: (BuildContext context) => Scaffold(
        appBar: CupertinoNavigationBar(
          middle: Text('Home'),
        ),
        body: Center(
          child: Text(
            'This is the home page',
            style: TextStyle(fontSize: 24),
          ),
        ),
      ),
    ),
    CupertinoTabView(
      builder: (BuildContext context) => Scaffold(
        appBar: CupertinoNavigationBar(
          middle: Text('Business'),
        ),
        body: Center(
          child: Text(
            'This is the business page',
            style: TextStyle(fontSize: 24),
          ),
        ),
      ),
    ),
    CupertinoTabView(
      builder: (BuildContext context) => Scaffold(
        appBar: CupertinoNavigationBar(
          middle: Text('School'),
        ),
        body: Center(
          child: Text(
            'This is the school page',
            style: TextStyle(fontSize: 24),
          ),
        ),
      ),
    ),
  ];

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return CupertinoTabScaffold(
      tabBar: CupertinoTabBar(
        items: <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            title: Text('Home'),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.business),
            title: Text('Business'),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.school),
            title: Text('School'),
          ),
        ],
        currentIndex: _selectedIndex,
        onTap: _onItemTapped,
      ),
      tabBuilder: (BuildContext context, int index) {
        return _widgetOptions[index];
      },
    );
  }
}

確かに別のページにはなっているようですが、ちょっと使いにくそうです。 そこで、もう一つお願いをしてみます。

各画面を別のクラスにして

出力されたコードは以下の通りです。

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      title: 'Cupertino TabBar Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _selectedIndex = 0;

  static List<Widget> _widgetOptions = <Widget>[
    HomeScreen(),
    BusinessScreen(),
    SchoolScreen(),
  ];

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return CupertinoTabScaffold(
      tabBar: CupertinoTabBar(
        items: <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            title: Text('Home'),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.business),
            title: Text('Business'),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.school),
            title: Text('School'),
          ),
        ],
        currentIndex: _selectedIndex,
        onTap: _onItemTapped,
      ),
      tabBuilder: (BuildContext context, int index) {
        return CupertinoTabView(
          builder: (BuildContext context) => _widgetOptions[index],
        );
      },
    );
  }
}

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: CupertinoNavigationBar(
        middle: Text('Home'),
      ),
      body: Center(
        child: Text(
          'This is the home page',
          style: TextStyle(fontSize: 24),
        ),
      ),
    );
  }
}

class BusinessScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: CupertinoNavigationBar(
        middle: Text('Business'),
      ),
      body: Center(
        child: Text(
          'This is the business page',
          style: TextStyle(fontSize: 24),
        ),
      ),
    );
  }
}

class SchoolScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: CupertinoNavigationBar(
        middle: Text('School'),
      ),
      body: Center(
        child: Text(
          'This is the school page',
          style: TextStyle(fontSize: 24),
        ),
      ),
    );
  }
}

これで各ページを切り離すことができました。相変わらず、labelがtitleになっていますので、そこだけは修正が必要です。動作結果は以下の通りです。

最終的なコードもシンプルなので、CupertinoTabBarの基本的な使い方を理解するのに役に立ちました。

CupertinoFullscreenDialogTransition

もう一つ、使いたかったWidgetがCupertinoFullscreenDialogTransitionです。

今まで入力用Dialog BoxとしてはAlertDialogを使っていたのですが、CupertinoAlertDialog にすると、幅が変更できませんでした。CupertinoAlertDialogを入力用のDialogBoxとして使うことは良くないようです。

そこで、CupertinoFullscreenDialogTransitionを試してみることにしました。

flutterでCupertinoFullscreenDialogTransitionを使って、テキスト入力のための画面を表示させるコードを書いて

残念ながらエラーが出ました。

The named parameter 'primaryRouteAnimation' is required, but there's no corresponding argument. Try adding the required argument.

The named parameter 'secondaryRouteAnimation' is required, but there's no corresponding argument. Try adding the required argument.

The named parameter 'linearTransition' is required, but there's no corresponding argument. Try adding the required argument.

パラメータが不足していたようです。このエラーメッセージをChatGPTに伝えます。

以下のようなエラーが出ました。

The named parameter 'primaryRouteAnimation' is required, but there's no corresponding argument. Try adding the required argument.

すると、エラーを修正したコードを返してきます。

最終的に出来上がったのが以下のコードです。

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home'),
      ),
      body: Center(
        child: CupertinoButton(
          child: Text('Open Text Input Screen'),
          onPressed: () {
            showTextInputScreen(context);
          },
        ),
      ),
    );
  }
}

class TextInputScreen extends StatefulWidget {
  @override
  _TextInputScreenState createState() => _TextInputScreenState();
}

class _TextInputScreenState extends State<TextInputScreen> with SingleTickerProviderStateMixin {
  final _textEditingController = TextEditingController();
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 300),
    );
    _animation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller);
    _controller.forward();
  }

  @override
  void dispose() {
    _textEditingController.dispose();
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return CupertinoFullscreenDialogTransition(
      primaryRouteAnimation: ModalRoute.of(context)!.animation!,
      secondaryRouteAnimation: ModalRoute.of(context)!.secondaryAnimation!,
      linearTransition: true,
      child: Scaffold(
        appBar: CupertinoNavigationBar(
          middle: Text('Enter Text'),
        ),
        body: SafeArea(
          child: Padding(
            padding: EdgeInsets.all(16.0),
            child: CupertinoTextField(
              controller: _textEditingController,
              placeholder: 'Enter some text',
            ),
          ),
        ),
      ),
    );
  }
}

void showTextInputScreen(BuildContext context) {
  Navigator.of(context, rootNavigator: true).push(
    CupertinoPageRoute(
      fullscreenDialog: true,
      builder: (BuildContext context) => TextInputScreen(),
    ),
  );
}

少し、修正が必要でしたが、以下のように実行することができました。

HOME PAGEのテキストボタンをクリックすると、下からEnter Textの画面が現れてきます。想定していた通りの動作です。

まとめ

ChatGPTを使うことで、Cupertino widgetsを使ったコードの雛形を得ることができました。プログラミングの学習ツールとして十分使えるかと思います。特に独学の場合、インターネット上の断片的な情報を繋ぎ合わせるのに時間がかかるのですが、それらを効率よく実行することができました。

実は、最初はあまり良いコードを書いてくれませんでしたが、使いたいクラス名などを指定することで、格段に良いコードになったかと思います。