{"id":15837,"date":"2019-04-29T09:19:34","date_gmt":"2019-04-29T07:19:34","guid":{"rendered":"https:\/\/www.inovex.de\/blog\/?p=15837"},"modified":"2026-03-17T07:59:23","modified_gmt":"2026-03-17T06:59:23","slug":"flutter-from-an-ios-perspective","status":"publish","type":"post","link":"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/","title":{"rendered":"Flutter from an iOS Perspective"},"content":{"rendered":"<p>Flutter\u00a0is an open source mobile SDK which can be used to build iOS and Android apps with the same code base. In this article we want to get a more detailed look from an iOS perspective to see how we can create Apps with a native iOS look and feel and how we can adapt our iOS Knowledge to Flutter.<!--more--><\/p>\n<p>My colleague Tino described the basics in his past few articles.\u00a0If you haven&#8217;t read them you can find them here:<\/p>\n<ul>\n<li>\n<p class=\"entry-title fusion-post-title\" data-fontsize=\"41\" data-lineheight=\"62\"><a href=\"https:\/\/www.inovex.de\/blog\/flutter-beginning-of-a-new-era-part-1\/\" target=\"_blank\" rel=\"noopener\">Flutter: The Beginning of a New Era? (Part\u00a01)<\/a><\/p>\n<\/li>\n<li>\n<p class=\"entry-title fusion-post-title\" data-fontsize=\"41\" data-lineheight=\"62\"><a href=\"https:\/\/www.inovex.de\/blog\/flutter-new-concepts-part-2\/\" target=\"_blank\" rel=\"noopener\">Flutter: New Concepts? (Part 2)<\/a><\/p>\n<\/li>\n<li>\n<p class=\"entry-title fusion-post-title\" data-fontsize=\"41\" data-lineheight=\"62\"><a href=\"https:\/\/www.inovex.de\/blog\/flutter-the-profiler-part-3\/\" target=\"_blank\" rel=\"noopener\">Flutter: The Profiler (Part\u00a03)<\/a><\/p>\n<\/li>\n<li>\n<p class=\"entry-title fusion-post-title\" data-fontsize=\"41\" data-lineheight=\"62\"><a href=\"https:\/\/www.inovex.de\/blog\/flutter-the-finalizer-part-4\/\" target=\"_blank\" rel=\"noopener\">Flutter: The Finalizer (Part 4)<\/a><\/p>\n<\/li>\n<\/ul>\n<p>Flutter is using Dart as a programming language and since the first stable version was released there are many apps available. If you want to get an impression you can see\u00a0a few apps created with Flutter on\u00a0<a href=\"https:\/\/itsallwidgets.com\/\" target=\"_blank\" rel=\"noopener\">this page<\/a>. Because Flutter is in active development you can see the Roadmap\u00a0<a href=\"https:\/\/github.com\/flutter\/flutter\/milestones\" target=\"_blank\" rel=\"noopener\">here<\/a>.<\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_82_2 counter-hierarchy ez-toc-counter ez-toc-custom ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\"><p class=\"ez-toc-title\" style=\"cursor:inherit\"><\/p>\n<\/div><nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#Project\" >Project<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#Package-Manager\" >Package Manager<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#Asynchronous-Code\" >Asynchronous Code<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#Architecture-Patterns\" >Architecture Patterns<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#Design\" >Design<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#Widgets\" >Widgets<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#Storyboard\" >Storyboard<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#iOS-Layout-Structures\" >iOS-Layout-Structures<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#UITabBarController\" >UITabBarController<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#UINavigationController\" >UINavigationController<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#Navigation\" >Navigation<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#UIBarButtonItem\" >UIBarButtonItem<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#UIAlertController\" >UIAlertController<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-14\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#UIWebViewSFSafariViewController\" >UIWebView\/SFSafariViewController<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-15\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#UITableView\" >UITableView<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-16\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#More-Cupertino-Widgets\" >More Cupertino Widgets<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-17\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#Good-to-know\" >Good to know<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-18\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#Execute-Native-Swift-Code\" >Execute Native Swift Code<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-19\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#Adding-a-pod-to-the-Flutter-CocoaPod-installation\" >Adding a pod to the Flutter CocoaPod installation<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-20\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#Method-channels-in-Swift\" >Method channels in Swift<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-21\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#Method-channels-in-Flutter\" >Method channels in Flutter<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-22\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#Network-Requests\" >Network Requests<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-23\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#User-Defaults\" >User Defaults<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-24\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#Fastlane\" >Fastlane<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-25\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#Conclusion\" >Conclusion<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"Project\"><\/span>Project<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>By default Flutter is creating an Xcode project in Objective-C. If we want to have our Xcode files in Swift we have to add the parameter <span class=\"lang:default decode:true crayon-inline\">-i swift<\/span>. To create a Flutter project we can execute the following statement in the terminal:\u00a0<span class=\"lang:default decode:true crayon-inline\">flutter create -i swift [projectname]<\/span>.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Package-Manager\"><\/span>Package Manager<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>The most widely used package managers in iOS are\u00a0third party tools like CocoaPods or Carthage. In the generated Xcode project Flutter is using CoocaPods as well. The dart packages are managed by the so-called\u00a0Pub Package Manager\u00a0and we can manage dependencies in the file <span class=\"lang:default decode:true crayon-inline\">pubspec.yaml<\/span>. When you add a package below <span class=\"lang:default decode:true crayon-inline \">dependencies<\/span>\u00a0it&#8217;s recognized as a package needed for our app to work. Dependencies needed only for development can be added below <span class=\"lang:default decode:true crayon-inline\">dev_dependencies<\/span>. Once you add a package you need to run the command <span class=\"lang:default decode:true crayon-inline\">flutter packages get<\/span>.\u00a0A lot of available Dart packages are listed\u00a0<a href=\"https:\/\/pub.dartlang.org\/\" target=\"_blank\" rel=\"noopener\">here<\/a>.<\/p>\n<pre class=\"lang:default decode:true\">dependencies:\r\n\r\n  http: ^0.12.0+1\r\n\r\n  url_launcher: ^5.0.1\r\n\r\n  shared_preferences: ^0.5.1+1\r\n\r\n  flutter_cupertino_settings: ^0.1.0\r\n\r\n  ...\r\n\r\ndev_dependencies:\r\n\r\n  ...<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"Asynchronous-Code\"><\/span>Asynchronous Code<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>In Dart you can use\u00a0<em>async\/await <\/em>to execute\u00a0<em>asynchronous<\/em>\u00a0code. In Swift the pendant for this is a closure or a c<span class=\"markup--quote markup--p-quote is-other\" data-creator-ids=\"anon\">allback<\/span>. It&#8217;s important to know that the code in Dart is only executed in a single thread. Asynchronous Code runs in separate isolates. Every isolate can run on a different CPU core but they can&#8217;t share memory. Instead they can only interact with each other by sending messages via ports (more information about\u00a0<a href=\"https:\/\/api.dartlang.org\/stable\/2.2.0\/dart-isolate\/ReceivePort-class.html\" target=\"_blank\" rel=\"noopener\">ReceivePort<\/a>\u00a0and\u00a0<a href=\"https:\/\/api.dartlang.org\/stable\/2.2.0\/dart-isolate\/SendPort-class.html\" target=\"_blank\" rel=\"noopener\">SendPort<\/a>).<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Architecture-Patterns\"><\/span>Architecture Patterns<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>In iOS we are using architecture patterns like\u00a0MVC, MVP, MVVM or Viper. In Flutter there are good alternatives called ScopedModel and BLoC (Business Logic Component). BLoC is created and used by Google and\u00a0ScopedModel\u00a0is a third party package. You can find both <a href=\"https:\/\/pub.dartlang.org\/\" target=\"_blank\" rel=\"noopener\">here<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Design\"><\/span>Design<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Flutter includes two specific design languages which contain platform specific widgets. Material design for Android and cupertino design for iOS. To use the cupertino design we have to import it in our dart files.<\/p>\n<pre class=\"lang:default decode:true\">import 'package:flutter\/cupertino.dart';<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"Widgets\"><\/span>Widgets<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>In iOS we are using the framework <span class=\"lang:default decode:true crayon-inline\">UIKit<\/span>, which provides view and window architecture,\u00a0event handling and different input types. Flutter has its own rendering engine which is using the concept of rendering a chain of widgets. There are two different types of widgets: <span class=\"lang:default decode:true crayon-inline \">stateful<\/span>\u00a0and <span class=\"lang:default decode:true crayon-inline\">stateless<\/span>. A stateful widget is dynamic, able to dynamically change its own content. A stateless widgets is the opposite and not dynamic. It only shows the data passed into its constructor at creation time.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Storyboard\"><\/span>Storyboard<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>In iOS we are mostly using storyboards to create our user interface. In Flutter the user interface is written in code by creating these widgets and combining them to a widget tree. After a while it feels very familiar and because a lot of code can be reused, it doesn&#8217;t need much more time than the native iOS method. One big advantage is the support of stateful hot reload. That means that changes in the source code can be shown immediately on device or in the simulator without restarting the App. If you don&#8217;t want to generate everything by hand you can use the tool <a href=\"https:\/\/flutterstudio.app\/\" target=\"_blank\" rel=\"noopener\">flutterstudio<\/a>.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"iOS-Layout-Structures\"><\/span>iOS-Layout-Structures<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Flutter supports a few iOS based layout structures. A layout structure has the ending <span class=\"lang:default decode:true crayon-inline \">Scaffold<\/span>\u00a0and it describes what the root layout and the\u00a0behavior structure should look like. <span class=\"lang:default decode:true crayon-inline\">CupertinoTabScaffold<\/span>\u00a0places the tab bar at the bottom, <span class=\"lang:default decode:true crayon-inline\">CupertinoPageScaffold<\/span>\u00a0places the navigation bar on top and the content is always placed in-between.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-16103\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/04\/cupertino-page-scaffold-600x1024.png\" alt=\"CupertinoPageScaffold\" width=\"235\" height=\"400\" srcset=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/04\/cupertino-page-scaffold-600x1024.png 600w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/04\/cupertino-page-scaffold-176x300.png 176w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/04\/cupertino-page-scaffold-768x1310.png 768w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/04\/cupertino-page-scaffold-400x682.png 400w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/04\/cupertino-page-scaffold-360x614.png 360w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/04\/cupertino-page-scaffold.png 819w\" sizes=\"auto, (max-width: 235px) 100vw, 235px\" \/> <img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-16104\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/04\/cupertino-tab-scaffold-601x1024.png\" alt=\"CupertinoTabScaffold\" width=\"235\" height=\"400\" \/><\/p>\n<h3><span class=\"ez-toc-section\" id=\"UITabBarController\"><\/span>UITabBarController<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>In iOS it makes sense to start the user interface with an <span class=\"lang:default decode:true crayon-inline\">UITabBarController<\/span>. In Flutter we can replicate this by creating a widget with a simple tab bar containing elements of type <span class=\"lang:default decode:true crayon-inline\">BottomNavigationBarItem<\/span>. We use the structure <span class=\"lang:default decode:true crayon-inline\">CupertinoTabScaffold<\/span>\u00a0with the widget <span class=\"lang:default decode:true crayon-inline\">CupertinoTabBar<\/span>. In <span class=\"lang:default decode:true crayon-inline \">tabBuilder<\/span>\u00a0 we have to define the content of the tab bar entries.<\/p>\n<pre class=\"lang:default decode:true\">final tabBarItems = [BottomNavigationBarItem(icon: Icon(CupertinoIcons.home), title: Text('Home')),\r\n\r\n                     BottomNavigationBarItem(icon: Icon(CupertinoIcons.book), title: Text('User')),\r\n\r\n                     BottomNavigationBarItem(icon: Icon(Icons.list), title: Text('Settings'))];\r\n\r\n@override\r\n\r\nWidget build(BuildContext context) {\r\n\r\n  return CupertinoTabScaffold(\r\n\r\n    tabBar: CupertinoTabBar(\r\n\r\n      items: tabBarItems,\r\n\r\n    ),\r\n\r\n    tabBuilder: (BuildContext context, int index) {\r\n\r\n      switch (index) {\r\n\r\n        case 0:\r\n\r\n          return new HomeView();\r\n\r\n        case 1:\r\n\r\n          return new UserView();\r\n\r\n        case 2:\r\n\r\n          return new SettingsView();\r\n\r\n      }\r\n\r\n      return null;\r\n\r\n    },\r\n\r\n  );\r\n\r\n}<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"UINavigationController\"><\/span>UINavigationController<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>In iOS the <span class=\"lang:default decode:true crayon-inline\">UINavigationController<\/span>\u00a0is one of the most used navigation components. It allows to create a navigation stack with push and pop view controllers inside and there is also a navigation bar on top with more information like a title and back button. When we want to add a\u00a0<span class=\"lang:default decode:true crayon-inline \">UINavigationBar<\/span>\u00a0we can do so by creating a <span class=\"lang:default decode:true crayon-inline\">CupertinoNavigationBar<\/span>\u00a0widget. We define this inside a\u00a0<span class=\"lang:default decode:true crayon-inline \">CupertinoPageScaffold<\/span>\u00a0layout structure.<\/p>\n<pre class=\"lang:default decode:true\">import 'package:flutter\/cupertino.dart';\r\n\r\nclass HomeView extends StatefulWidget {\r\n\r\n  @override\r\n\r\n  _HomeView createState() =&gt; _HomeView();\r\n\r\n}\r\n\r\nclass _HomeView extends State&lt;HomeView&gt; {\r\n\r\n  @override\r\n\r\n  Widget build(BuildContext context) {\r\n\r\n    return new CupertinoPageScaffold(navigationBar: _navBar(), child: DefaultTextStyle(style: CupertinoTheme.of(context).textTheme.textStyle, child: new Container()));\r\n\r\n  }\r\n\r\n  Widget _navBar() {\r\n\r\n    return CupertinoNavigationBar(previousPageTitle: \"Home\", middle: Text(\"Home\"));\r\n\r\n  }\r\n\r\n}<\/pre>\n<p>After placing the\u00a0<span class=\"lang:default decode:true crayon-inline\">CupertinoTabBar<\/span>,\u00a0<span class=\"lang:default decode:true crayon-inline\">CupertinoNavigationBar<\/span>\u00a0widgets inside the\u00a0<span class=\"lang:default decode:true crayon-inline\">CupertinoTabScaffold<\/span>\u00a0and <span class=\"lang:default decode:true crayon-inline \">CupertinoPageScaffold<\/span>\u00a0layout structure, the Flutter app looks like this:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-15858\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-1.png\" alt=\"TabBar\" width=\"226\" height=\"400\" \/><\/p>\n<h3><span class=\"ez-toc-section\" id=\"Navigation\"><\/span>Navigation<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>To add navigation options, eg. pushing a screen with the name <span class=\"lang:default decode:true crayon-inline \">&#8222;\/detail&#8220;<\/span>\u00a0to our stack, we can use following method.<\/p>\n<pre class=\"lang:default decode:true\">Navigator.of(context).pushNamed('\/detail');<\/pre>\n<p>To pop this screen we can use.<\/p>\n<pre class=\"lang:default decode:true\">Navigator.of(context).pop();<\/pre>\n<p>We can add all the routes we want to use in our <span class=\"lang:default decode:true crayon-inline \">main.dart<\/span>\u00a0file.<\/p>\n<pre class=\"lang:default decode:true\">routes: &lt;String, WidgetBuilder&gt; {\r\n\r\n  '\/detail': (BuildContext context) =&gt; new DetailView()\r\n\r\n},<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"UIBarButtonItem\"><\/span>UIBarButtonItem<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>In iOS we can add <span class=\"lang:default decode:true crayon-inline \">UIBarButtonItems<\/span>\u00a0to our navigation bar on the right\/left as <span class=\"lang:default decode:true crayon-inline \">rightBarButtonItem<\/span>\u00a0or <span class=\"lang:default decode:true crayon-inline\">leftBarButtonItem<\/span>. In Flutter we can add a <span class=\"lang:default decode:true crayon-inline \">CupertinoButton<\/span>\u00a0as <span class=\"lang:default decode:true crayon-inline \">trailing<\/span>\u00a0to our navigation bar to achieve a similar look to a <span class=\"lang:default decode:true crayon-inline\">rightBarButtonItem<\/span>.<\/p>\n<pre class=\"lang:default decode:true\">Widget _rightBarButtonItem() {\r\n\r\n  return CupertinoButton(child: Icon(CupertinoIcons.info), onPressed: () {});\r\n\r\n}\r\n\r\nWidget _navBar() {\r\n\r\n  return CupertinoNavigationBar(previousPageTitle: \"Home\", middle: Text(\"Home\"), trailing: _rightBarButtonItem());\r\n\r\n}<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"UIAlertController\"><\/span>UIAlertController<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>In iOS we can show additional information with an <span class=\"lang:default decode:true crayon-inline\">UIAlertController<\/span>. There are two possible styles: <span class=\"lang:default decode:true crayon-inline \">actionSheet<\/span>\u00a0and <span class=\"lang:default decode:true crayon-inline\">alert<\/span>. In Flutter we have the <span class=\"lang:default decode:true crayon-inline \">CupertinoAlertDialog<\/span>\u00a0and <span class=\"lang:default decode:true crayon-inline \">CupertinoActionSheet<\/span>\u00a0widgets which can do this job. The following example opens a <span class=\"lang:default decode:true crayon-inline \">CupertinoAlertDialog<\/span>\u00a0when we are pressing the <span class=\"lang:default decode:true crayon-inline \">rightBarButtonItem<\/span>\u00a0in our navigation bar.<\/p>\n<pre class=\"lang:default decode:true\">Widget _rightBarButtonItem(BuildContext context) {\r\n\r\n  return CupertinoButton(child: Icon(CupertinoIcons.info), onPressed: () {\r\n\r\n    showCupertinoDialog(context: context, builder: (context) { return _alert(context); });\r\n\r\n  });\r\n\r\n}\r\n\r\nWidget _alert(BuildContext context) {\r\n\r\n  return CupertinoAlertDialog(title: Text('Info'), content: Text('This is an information text.'), actions: &lt;Widget&gt;[new CupertinoButton(child: Text(\"OK\"), onPressed: () {\r\n\r\n    Navigator.pop(context);\r\n\r\n  })],);\r\n\r\n}<\/pre>\n<p>we can show an action sheet with a smiliar procedure. We have to use <span class=\"lang:default decode:true crayon-inline \">showCupertinoModalPopup<\/span>\u00a0instead of <span class=\"lang:default decode:true crayon-inline \">showCupertinoDialog<\/span>\u00a0and instead of a content field\u00a0we only have\u00a0<span class=\"lang:default decode:true crayon-inline\">actions<\/span>\u00a0and <span class=\"lang:default decode:true crayon-inline\">cancelButton<\/span>.<\/p>\n<pre class=\"lang:default decode:true\">Widget _rightBarButtonItem(BuildContext context) {\r\n\r\n  return CupertinoButton(child: Icon(CupertinoIcons.info), onPressed: () {\r\n\r\n    showCupertinoModalPopup(context: context, builder: (context) { return _actionSheet(context); });\r\n\r\n  });\r\n\r\n}\r\n\r\nWidget _actionSheet(BuildContext context) {\r\n\r\n  return CupertinoActionSheet(title: Text(\"Title\"),\r\n\r\n                            actions: &lt;Widget&gt;[new CupertinoButton(child: Text(\"Option 1\"), onPressed: () {}), new CupertinoButton(child: Text(\"Option 2\"), onPressed: () {})],\r\n\r\n                       cancelButton: new CupertinoButton(child: Text(\"Cancel\"), onPressed: () {}));\r\n\r\n}<\/pre>\n<h3><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-15861\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-2.png\" alt=\"Alert\" width=\"226\" height=\"400\" srcset=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-2.png 1122w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-2-169x300.png 169w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-2-577x1024.png 577w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-2-768x1362.png 768w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-2-866x1536.png 866w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-2-400x709.png 400w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-2-360x639.png 360w\" sizes=\"auto, (max-width: 226px) 100vw, 226px\" \/><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-15866\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-3-575x1024.png\" alt=\"Alert\" width=\"225\" height=\"400\" srcset=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-3-575x1024.png 575w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-3-169x300.png 169w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-3-768x1367.png 768w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-3-863x1536.png 863w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-3-1151x2048.png 1151w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-3-400x712.png 400w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-3-360x641.png 360w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-3.png 1172w\" sizes=\"auto, (max-width: 225px) 100vw, 225px\" \/><\/h3>\n<h3><span class=\"ez-toc-section\" id=\"UIWebViewSFSafariViewController\"><\/span>UIWebView\/SFSafari<wbr \/>View<wbr \/>Controller<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>We can integrate a\u00a0<span class=\"lang:default decode:true crayon-inline\">UIWebView<\/span>\u00a0with the <span class=\"lang:default decode:true crayon-inline \">webview_flutter<\/span>\u00a0package.\u00a0The simplest implementation is to create a <span class=\"lang:default decode:true crayon-inline \">WebView<\/span>\u00a0widget with the parameter <span class=\"lang:default decode:true crayon-inline \">initialUrl<\/span>\u00a0which is added as a child to <span class=\"lang:default decode:true crayon-inline\">CupertinoPageScaffold<\/span>.<\/p>\n<pre class=\"lang:default decode:true\">@override\r\n\r\nWidget build(BuildContext context) {\r\n\r\n  return new CupertinoPageScaffold(navigationBar: _navBar(context), child: DefaultTextStyle(style: CupertinoTheme.of(context).textTheme.textStyle, child: SafeArea(child: _webView())));\r\n\r\n}\r\n\r\nWidget _webView() {\r\n\r\n  return WebView(initialUrl: 'https:\/\/www.inovex.de');\r\n\r\n}<\/pre>\n<p>Instead of embedding web content we can open it in an external controller such as\u00a0<span class=\"lang:default decode:true crayon-inline\">SFSafariViewController<\/span>. To do so we can use the <span class=\"lang:default decode:true crayon-inline \">url_launcher<\/span>\u00a0package.<\/p>\n<pre class=\"lang:default decode:true\">_launchURL() async {\r\n\r\n  const url = 'https:\/\/www.inovex.de';\r\n\r\n  if (await canLaunch(url)) {\r\n\r\n    await launch(url);\r\n\r\n  } else {\r\n\r\n    throw 'Could not launch $url';\r\n\r\n  }\r\n\r\n}<\/pre>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-15869\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-4-575x1024.png\" alt=\"WebView\" width=\"225\" height=\"400\" \/><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-15870\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-5-575x1024.png\" alt=\"WebView\" width=\"225\" height=\"400\" \/><\/p>\n<h3><span class=\"ez-toc-section\" id=\"UITableView\"><\/span>UITableView<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>In iOS we use <span class=\"lang:default decode:true crayon-inline \">UITableView<\/span>\u00a0to display the content using rows arranged in a single column. We have datasource and delegate methods for UITableView: <em>datasource<\/em> to provide data that controls the state of the table view and <em>delegate<\/em> controls how to use the data and manages interactions. In Flutter instead of a table view there is <span class=\"lang:default decode:true crayon-inline\">ListView<\/span><em>.\u00a0<\/em>It takes a list of children and displays them in the<i>\u00a0<\/i>scroll\u00a0direction.<\/p>\n<pre class=\"lang:default decode:true\">Widget _listView() {\r\n\r\nreturn ListView.builder(\r\n\r\n  itemCount: itemCount,\r\n\r\n  itemBuilder: (context, position) {\r\n\r\n    return listItem();\r\n\r\n  },\r\n\r\n)\r\n\r\n}\r\n\r\n<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"More-Cupertino-Widgets\"><\/span>More Cupertino Widgets<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>There are a lot of more iOS widgets in the cupertino design package. In the following example we are using <span class=\"lang:default decode:true crayon-inline\">UISwitch<\/span>, <span class=\"lang:default decode:true crayon-inline \">UISlider<\/span>\u00a0and <span class=\"lang:default decode:true crayon-inline \">UISegmentedControl<\/span>\u00a0as well as an\u00a0<span class=\"lang:default decode:true crayon-inline\">MKMapView<\/span>\u00a0-like component from the <span class=\"lang:default decode:true crayon-inline \">map_native<\/span>\u00a0package.<\/p>\n<pre class=\"lang:default decode:true\">\/\/ UISwitch\r\n\r\nWidget _switch() {\r\n\r\n  return CupertinoSwitch(value: true, onChanged: (value) {});\r\n\r\n}\r\n\r\n\/\/ UISlider\r\n\r\nWidget _slider() {\r\n\r\n  return CupertinoSlider(value: 0.5, onChanged: (value) {});\r\n\r\n}\r\n\r\n\/\/ UISegmentedControl\r\n\r\nWidget _segmentedControl() {\r\n\r\n  return CupertinoSegmentedControl(children: children, onValueChanged: (value) {});\r\n\r\n}\r\n\r\n\/\/ MKMapView\r\n\r\nvar _mapView = new MapView(initialLocation: const LatLong(49.006889, 8.403653));\r\n\r\n@override\r\n\r\nWidget build(BuildContext context) {\r\n\r\n  return CupertinoPageScaffold(navigationBar: _navBar(context), child: SafeArea(child: _mapView));\r\n\r\n}\r\n\r\nWidget _navBar(BuildContext context) {\r\n\r\n  return CupertinoNavigationBar(previousPageTitle: \"User\", middle: Text(\"User\"));\r\n\r\n}<\/pre>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-15879\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-8-575x1024.png\" alt=\"MoreCupertinoWidgets\" width=\"225\" height=\"400\" srcset=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-8-575x1024.png 575w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-8-169x300.png 169w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-8-768x1367.png 768w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-8-863x1536.png 863w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-8-1151x2048.png 1151w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-8-400x712.png 400w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-8-360x641.png 360w, https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-8.png 1172w\" sizes=\"auto, (max-width: 225px) 100vw, 225px\" \/><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-15874\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-7-575x1024.png\" alt=\"Map\" width=\"225\" height=\"400\" \/><\/p>\n<h2><span class=\"ez-toc-section\" id=\"Good-to-know\"><\/span>Good to know<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>In the next step we want to look at a few specific things needed in almost every iOS project. We want to execute native swift code, add our own pods to the CocoaPods installation created by Flutter and we want to take a quick look at fastlane integration.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Execute-Native-Swift-Code\"><\/span>Execute Native Swift Code<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>When we want to execute Swift Code from Flutter we can do so with a <span class=\"lang:default decode:true crayon-inline\">FlutterMethodChannel<\/span>. In our next example we are getting the device information, once with Swift passing to Flutter and once with the <span class=\"lang:default decode:true crayon-inline \">device_info<\/span>\u00a0<span class=\"hljs-attr\">plugin directly. When we create a flutter project, flutter is automatically adding a CocoaPods installation to our Xcode project and is managing its own dependencies with it.<\/span><\/p>\n<h3><span class=\"ez-toc-section\" id=\"Adding-a-pod-to-the-Flutter-CocoaPod-installation\"><\/span>Adding a pod to the Flutter CocoaPod installation<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>We can simply add our own pod by adding it to the <span class=\"lang:default decode:true crayon-inline \">Podfile<\/span>\u00a0in the iOS subfolder. In the following example we added the <span class=\"lang:default decode:true crayon-inline\">pod &#8218;Device&#8216;<\/span>.<\/p>\n<pre class=\"lang:default decode:true\">...\r\n\r\ntarget 'Runner' do\r\n\r\n  use_frameworks!\r\n\r\n  pod \"Device\"\r\n\r\n  # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock\r\n\r\n  # referring to absolute paths on developers' machines.\r\n\r\n  system('rm -rf .symlinks')\r\n\r\n  system('mkdir -p .symlinks\/plugins')\r\n\r\n  ...<\/pre>\n<p>We need to add the following lines to\u00a0<span class=\"lang:default decode:true crayon-inline \">Debug.xcconfig<\/span>\u00a0and <span class=\"lang:default decode:true crayon-inline \">Release.xcconfig<\/span>\u00a0so that Xcode can find the added pods.<\/p>\n<pre class=\"lang:default decode:true \">#include \"Pods\/Target Support Files\/Pods-Runner\/Pods-Runner.debug.xcconfig\"<\/pre>\n<pre class=\"lang:default decode:true \">#include \"Pods\/Target Support Files\/Pods-Runner\/Pods-Runner.release.xcconfig\"<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"Method-channels-in-Swift\"><\/span>Method channels in Swift<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>After this we can add\u00a0a <span class=\"lang:default decode:true crayon-inline \">FlutterMethodChannel<\/span>\u00a0in our AppDelegate by adding it\u00a0in <span class=\"lang:default decode:true crayon-inline\">applicationDidFinishLaunchingWithOptions<\/span>. The name &#8222;samples.inovex.com\/native&#8220;\u00a0identifies the channel. The binaryMessenger is a facility for sending raw messages to Flutter.<\/p>\n<pre class=\"lang:default decode:true\">let controller: FlutterViewController = window?.rootViewController as! FlutterViewController\r\n\r\nlet nativeChannel = FlutterMethodChannel(name: \"samples.inovex.com\/native\", binaryMessenger: controller)<\/pre>\n<p>We need to add a method call handler to our <span class=\"lang:default decode:true crayon-inline \">FlutterMethodChannel<\/span>\u00a0to handle defined methods. In our case we specified the method &#8222;deviceVersion&#8220;, which is given the return value from the method <span class=\"lang:default decode:true crayon-inline \">deviceVersion()<\/span>\u00a0as a <span class=\"lang:default decode:true crayon-inline \">FlutterResult<\/span>\u00a0to Flutter.<\/p>\n<pre class=\"lang:default decode:true\">nativeChannel.setMethodCallHandler({ [weak self] (call: FlutterMethodCall, result: FlutterResult) -&gt; Void in\r\n\r\n  if call.method == \"deviceVersion\" {\r\n\r\n    result(self?.deviceVersion())\r\n\r\n  } else {\r\n\r\n    result(FlutterMethodNotImplemented)\r\n\r\n  }\r\n\r\n})<\/pre>\n<pre class=\"lang:default decode:true\">private func deviceVersion() -&gt; String {\r\n\r\n  return String(Device.version().rawValue)\r\n\r\n}<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"Method-channels-in-Flutter\"><\/span>Method channels in Flutter<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Back in Flutter we can access the defined method from our channel with these lines:<\/p>\n<pre class=\"lang:default decode:true\">static const platform = const MethodChannel('samples.inovex.com\/native');\r\n\r\nFuture&lt;void&gt; _getNativeDeviceVersion() async {\r\n\r\n  try {\r\n\r\n    final String result = await platform.invokeMethod('deviceVersion');\r\n\r\n    setState(() {\r\n\r\n      _nativeDeviceVersion = \"$result\";\r\n\r\n    });\r\n\r\n  } on PlatformException catch (e) {\r\n\r\n    print(\"Failed to get device version '${e.message}'.\");\r\n\r\n  }\r\n\r\n}<\/pre>\n<p>We can get the same information directly with the <span class=\"lang:default decode:true crayon-inline \">DeviceInfoPlugin<\/span>\u00a0from Flutter.<\/p>\n<pre class=\"lang:default decode:true\">void _getDeviceVersion() async {\r\n\r\n  DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();\r\n\r\n  IosDeviceInfo iosInfo = await deviceInfo.iosInfo;\r\n\r\n  setState(() {\r\n\r\n    _deviceVersion = iosInfo.model;\r\n\r\n  });\r\n\r\n}<\/pre>\n<p>When we change code in Swift we can&#8217;t use the hot reload feature in Flutter. We have to quit and restart the app to see the changes. In the following screenshot we have two labels, one with the output from the native Swift code and one with the output directly from Flutter.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-15882\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/03\/Flutter-iOS-10-585x1024.png\" alt=\"FlutterMethodChannel\" width=\"228\" height=\"400\" \/><\/p>\n<h3><span class=\"ez-toc-section\" id=\"Network-Requests\"><\/span>Network Requests<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>For network requests we can use the <span class=\"lang:default decode:true crayon-inline \">http<\/span>\u00a0package and for JSON decoding we have to <span class=\"lang:default decode:true crayon-inline\">import dart:convert<\/span>.\u00a0A simple network request can be made with the <span class=\"lang:default decode:true crayon-inline \">get<\/span>\u00a0method from <span class=\"lang:default decode:true crayon-inline\">http<\/span>.<\/p>\n<pre class=\"lang:default decode:true \">_loadData() async {\r\n\r\n  String url = \"https:\/\/jsonplaceholder.typicode.com\/posts\";\r\n\r\n  http.Response response = await http.get(url);\r\n\r\n  setState(() {\r\n\r\n    widgets = json.decode(response.body);\r\n\r\n  });\r\n\r\n}<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"User-Defaults\"><\/span>User Defaults<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>In Flutter can we use <span class=\"lang:default decode:true crayon-inline \">SharedPreferences<\/span>\u00a0for storing and reading data from <span class=\"lang:default decode:true crayon-inline\">UserDefaults<\/span>.<\/p>\n<pre class=\"lang:default decode:true\">_read() async {\r\n\r\n  SharedPreferences sharedPreferences = await SharedPreferences.getInstance();\r\n\r\n  return (sharedPreferences.getInt('counter') ?? 0);\r\n\r\n}\r\n\r\n_store(int counter) async {\r\n\r\n  SharedPreferences sharedPreferences = await SharedPreferences.getInstance();\r\n\r\n  await sharedPreferences.setInt('counter', counter);\r\n\r\n}<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"Fastlane\"><\/span>Fastlane<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>When the project can be built successfully from command line with the command\u00a0<span class=\"lang:default decode:true crayon-inline \">flutter build ios &#8211;release &#8211;no-codesign<\/span>\u00a0we can start with the fastlane configuration. The initial setup can be made with <span class=\"lang:default decode:true crayon-inline\">fastlane init<\/span>\u00a0from the ios subdirectory. After this we can edit <span class=\"lang:default decode:true crayon-inline \">Appfile<\/span>\u00a0and <span class=\"lang:default decode:true crayon-inline \">Fastfile<\/span>\u00a0and start a build with\u00a0<span class=\"lang:default decode:true crayon-inline\">fastlane [name of the lane]<\/span>. This can be easily integrated in a continuous integration pipeline like Jenkins or GitLab CI.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Conclusion\"><\/span>Conclusion<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Flutter is a very interesting technology and is getting a lot of attention since December 2018, when Google made the first stable version public. Cross-platform development is very popular because you don&#8217;t need more teams with different technology stacks. You can build everything with one team and one code base which is reducing cost and complexity. Once you understand the concept of Flutter you can develop effectively and with a lot of fun. Of course there are limitations but it&#8217;s a young technology with a promising future. We will follow the technology and we are\u00a0curious\u00a0how it will develop in the future.<\/p>\n<p>Find out more about our\u00a0<a href=\"https:\/\/www.inovex.de\/en\/our-services\/apps\/\">Android and iOS portfolio<\/a>\u00a0or\u00a0join us\u00a0as a mobile developer!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Flutter\u00a0is an open source mobile SDK which can be used to build iOS and Android apps with the same code base. In this article we want to get a more detailed look from an iOS perspective to see how we can create Apps with a native iOS look and feel and how we can adapt [&hellip;]<\/p>\n","protected":false},"author":104,"featured_media":16190,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"ep_exclude_from_search":false,"footnotes":""},"tags":[510],"service":[420],"coauthors":[{"id":104,"display_name":"Bj\u00f6rn Michael Solvan Theilmann","user_nicename":"btheilmann"}],"class_list":["post-15837","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","tag-apps-2","service-apps"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Flutter from an iOS Perspective - inovex GmbH<\/title>\n<meta name=\"description\" content=\"Flutter\u00a0is an open source mobile SDK which can be used to build iOS and Android apps with the same code base. In this article we want to get a more detailed look from an iOS perspective to see how we can create Apps with a native iOS look and feel and how we can adapt our iOS knowledge to Flutter.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/\" \/>\n<meta property=\"og:locale\" content=\"de_DE\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Flutter from an iOS Perspective - inovex GmbH\" \/>\n<meta property=\"og:description\" content=\"Flutter\u00a0is an open source mobile SDK which can be used to build iOS and Android apps with the same code base. In this article we want to get a more detailed look from an iOS perspective to see how we can create Apps with a native iOS look and feel and how we can adapt our iOS knowledge to Flutter.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/\" \/>\n<meta property=\"og:site_name\" content=\"inovex GmbH\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/inovexde\" \/>\n<meta property=\"article:published_time\" content=\"2019-04-29T07:19:34+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-03-17T06:59:23+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/04\/flutter-ios.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1920\" \/>\n\t<meta property=\"og:image:height\" content=\"1080\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Bj\u00f6rn Michael Solvan Theilmann\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/04\/flutter-ios-1024x576.png\" \/>\n<meta name=\"twitter:creator\" content=\"@inovexgmbh\" \/>\n<meta name=\"twitter:site\" content=\"@inovexgmbh\" \/>\n<meta name=\"twitter:label1\" content=\"Verfasst von\" \/>\n\t<meta name=\"twitter:data1\" content=\"Bj\u00f6rn Michael Solvan Theilmann\" \/>\n\t<meta name=\"twitter:label2\" content=\"Gesch\u00e4tzte Lesezeit\" \/>\n\t<meta name=\"twitter:data2\" content=\"12\u00a0Minuten\" \/>\n\t<meta name=\"twitter:label3\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data3\" content=\"Bj\u00f6rn Michael Solvan Theilmann\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/flutter-from-an-ios-perspective\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/flutter-from-an-ios-perspective\\\/\"},\"author\":{\"name\":\"Bj\u00f6rn Michael Solvan Theilmann\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/person\\\/c218b31a8c402f08160db43a45896263\"},\"headline\":\"Flutter from an iOS Perspective\",\"datePublished\":\"2019-04-29T07:19:34+00:00\",\"dateModified\":\"2026-03-17T06:59:23+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/flutter-from-an-ios-perspective\\\/\"},\"wordCount\":1770,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/flutter-from-an-ios-perspective\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2019\\\/04\\\/flutter-ios.png\",\"keywords\":[\"Apps\"],\"articleSection\":[\"Applications\",\"English Content\",\"General\"],\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/flutter-from-an-ios-perspective\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/flutter-from-an-ios-perspective\\\/\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/flutter-from-an-ios-perspective\\\/\",\"name\":\"Flutter from an iOS Perspective - inovex GmbH\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/flutter-from-an-ios-perspective\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/flutter-from-an-ios-perspective\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2019\\\/04\\\/flutter-ios.png\",\"datePublished\":\"2019-04-29T07:19:34+00:00\",\"dateModified\":\"2026-03-17T06:59:23+00:00\",\"description\":\"Flutter\u00a0is an open source mobile SDK which can be used to build iOS and Android apps with the same code base. In this article we want to get a more detailed look from an iOS perspective to see how we can create Apps with a native iOS look and feel and how we can adapt our iOS knowledge to Flutter.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/flutter-from-an-ios-perspective\\\/#breadcrumb\"},\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/flutter-from-an-ios-perspective\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/flutter-from-an-ios-perspective\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2019\\\/04\\\/flutter-ios.png\",\"contentUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2019\\\/04\\\/flutter-ios.png\",\"width\":1920,\"height\":1080,\"caption\":\"An iPhone X with a Flutter app icon\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/flutter-from-an-ios-perspective\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Flutter from an iOS Perspective\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#website\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/\",\"name\":\"inovex GmbH\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"de\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#organization\",\"name\":\"inovex GmbH\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2021\\\/03\\\/inovex-logo-16-9-1.png\",\"contentUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2021\\\/03\\\/inovex-logo-16-9-1.png\",\"width\":1921,\"height\":1081,\"caption\":\"inovex GmbH\"},\"image\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/inovexde\",\"https:\\\/\\\/x.com\\\/inovexgmbh\",\"https:\\\/\\\/www.instagram.com\\\/inovexlife\\\/\",\"https:\\\/\\\/www.linkedin.com\\\/company\\\/inovex\",\"https:\\\/\\\/www.youtube.com\\\/channel\\\/UC7r66GT14hROB_RQsQBAQUQ\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/person\\\/c218b31a8c402f08160db43a45896263\",\"name\":\"Bj\u00f6rn Michael Solvan Theilmann\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/cropped-btheilmann-professional-2024-2-96x96.jpg44248aedb637d7692bed6f1f6e9a7571\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/cropped-btheilmann-professional-2024-2-96x96.jpg\",\"contentUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/cropped-btheilmann-professional-2024-2-96x96.jpg\",\"caption\":\"Bj\u00f6rn Michael Solvan Theilmann\"},\"sameAs\":[\"https:\\\/\\\/theilmann.me\\\/\",\"https:\\\/\\\/www.linkedin.com\\\/in\\\/bjoernmichaelsolvan\\\/\"],\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/author\\\/btheilmann\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Flutter from an iOS Perspective - inovex GmbH","description":"Flutter\u00a0is an open source mobile SDK which can be used to build iOS and Android apps with the same code base. In this article we want to get a more detailed look from an iOS perspective to see how we can create Apps with a native iOS look and feel and how we can adapt our iOS knowledge to Flutter.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/","og_locale":"de_DE","og_type":"article","og_title":"Flutter from an iOS Perspective - inovex GmbH","og_description":"Flutter\u00a0is an open source mobile SDK which can be used to build iOS and Android apps with the same code base. In this article we want to get a more detailed look from an iOS perspective to see how we can create Apps with a native iOS look and feel and how we can adapt our iOS knowledge to Flutter.","og_url":"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/","og_site_name":"inovex GmbH","article_publisher":"https:\/\/www.facebook.com\/inovexde","article_published_time":"2019-04-29T07:19:34+00:00","article_modified_time":"2026-03-17T06:59:23+00:00","og_image":[{"width":1920,"height":1080,"url":"https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/04\/flutter-ios.png","type":"image\/png"}],"author":"Bj\u00f6rn Michael Solvan Theilmann","twitter_card":"summary_large_image","twitter_image":"https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/04\/flutter-ios-1024x576.png","twitter_creator":"@inovexgmbh","twitter_site":"@inovexgmbh","twitter_misc":{"Verfasst von":"Bj\u00f6rn Michael Solvan Theilmann","Gesch\u00e4tzte Lesezeit":"12\u00a0Minuten","Written by":"Bj\u00f6rn Michael Solvan Theilmann"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#article","isPartOf":{"@id":"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/"},"author":{"name":"Bj\u00f6rn Michael Solvan Theilmann","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/person\/c218b31a8c402f08160db43a45896263"},"headline":"Flutter from an iOS Perspective","datePublished":"2019-04-29T07:19:34+00:00","dateModified":"2026-03-17T06:59:23+00:00","mainEntityOfPage":{"@id":"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/"},"wordCount":1770,"commentCount":0,"publisher":{"@id":"https:\/\/www.inovex.de\/de\/#organization"},"image":{"@id":"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#primaryimage"},"thumbnailUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/04\/flutter-ios.png","keywords":["Apps"],"articleSection":["Applications","English Content","General"],"inLanguage":"de","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/","url":"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/","name":"Flutter from an iOS Perspective - inovex GmbH","isPartOf":{"@id":"https:\/\/www.inovex.de\/de\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#primaryimage"},"image":{"@id":"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#primaryimage"},"thumbnailUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/04\/flutter-ios.png","datePublished":"2019-04-29T07:19:34+00:00","dateModified":"2026-03-17T06:59:23+00:00","description":"Flutter\u00a0is an open source mobile SDK which can be used to build iOS and Android apps with the same code base. In this article we want to get a more detailed look from an iOS perspective to see how we can create Apps with a native iOS look and feel and how we can adapt our iOS knowledge to Flutter.","breadcrumb":{"@id":"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#breadcrumb"},"inLanguage":"de","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/"]}]},{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#primaryimage","url":"https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/04\/flutter-ios.png","contentUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/2019\/04\/flutter-ios.png","width":1920,"height":1080,"caption":"An iPhone X with a Flutter app icon"},{"@type":"BreadcrumbList","@id":"https:\/\/www.inovex.de\/de\/blog\/flutter-from-an-ios-perspective\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.inovex.de\/de\/"},{"@type":"ListItem","position":2,"name":"Flutter from an iOS Perspective"}]},{"@type":"WebSite","@id":"https:\/\/www.inovex.de\/de\/#website","url":"https:\/\/www.inovex.de\/de\/","name":"inovex GmbH","description":"","publisher":{"@id":"https:\/\/www.inovex.de\/de\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.inovex.de\/de\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"de"},{"@type":"Organization","@id":"https:\/\/www.inovex.de\/de\/#organization","name":"inovex GmbH","url":"https:\/\/www.inovex.de\/de\/","logo":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/logo\/image\/","url":"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/03\/inovex-logo-16-9-1.png","contentUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/03\/inovex-logo-16-9-1.png","width":1921,"height":1081,"caption":"inovex GmbH"},"image":{"@id":"https:\/\/www.inovex.de\/de\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/inovexde","https:\/\/x.com\/inovexgmbh","https:\/\/www.instagram.com\/inovexlife\/","https:\/\/www.linkedin.com\/company\/inovex","https:\/\/www.youtube.com\/channel\/UC7r66GT14hROB_RQsQBAQUQ"]},{"@type":"Person","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/person\/c218b31a8c402f08160db43a45896263","name":"Bj\u00f6rn Michael Solvan Theilmann","image":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/www.inovex.de\/wp-content\/uploads\/cropped-btheilmann-professional-2024-2-96x96.jpg44248aedb637d7692bed6f1f6e9a7571","url":"https:\/\/www.inovex.de\/wp-content\/uploads\/cropped-btheilmann-professional-2024-2-96x96.jpg","contentUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/cropped-btheilmann-professional-2024-2-96x96.jpg","caption":"Bj\u00f6rn Michael Solvan Theilmann"},"sameAs":["https:\/\/theilmann.me\/","https:\/\/www.linkedin.com\/in\/bjoernmichaelsolvan\/"],"url":"https:\/\/www.inovex.de\/de\/blog\/author\/btheilmann\/"}]}},"_links":{"self":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts\/15837","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/users\/104"}],"replies":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/comments?post=15837"}],"version-history":[{"count":2,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts\/15837\/revisions"}],"predecessor-version":[{"id":66505,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts\/15837\/revisions\/66505"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/media\/16190"}],"wp:attachment":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/media?parent=15837"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/tags?post=15837"},{"taxonomy":"service","embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/service?post=15837"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/coauthors?post=15837"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}