Recreating Settings App using SwiftUI Concepts

Shankar Madeshvaran
Jul 1 · 10 min read
Recreated Setttings App — iPhone XR

One of the latest framework that Apple has introduced in WWDC19 is SwiftUI and Combine.

If incase you missed it, SwiftUI is a new way for making you UI in a declarative way. Combine works along with SwiftUI and provides a declarative Swift API for processing values such as UI or Network events.

For SwiftUI beginners,I recommend you to read this article which explains the basic’s of SwiftUI and this articles which shows how to work with SwiftUI.

In this project, we are going to recreate Settings app in iPhone XR using SwiftUI concepts.

By building Settings App, we are going to learn about:

  • How to work with Forms, Sections, Stack, Group in SwiftUI.
  • How to work with Picker and Toggle in SwiftUI.
  • How to work with Navigation and UIActivityIndicator in SwiftUI.
  • How to Control and respond to the flow of data and changes within your app’s models (ObjectBinding) in SwiftUI.

1) Model class

Model Class

Option

  • Option — Denotes each setting in Settings App (eg: General, Accessibility,Privacy..etc)
  • title — Denotes the title of settings
  • isAddSection — Denotes whether the particular setting needs to be show in seperate section
  • values — Array of innerSettings options(For Accessibility: Display,Motion,Spoken Content,Touch..etc)

  • InnerOptionValues — Denotes inner Settings option for primary setting (eg: For Accessibility: Display,Motion,Spoken Content,Touch..etc)
  • title — Denotes the title of inner settings.
  • isAddSection — Denotes whether the particular inner setting needs to be show in seperate section or not.
  • isUseToggle — Denotes whether the particular inner setting view needs to be toggle or not.
  • headerTitle — To add title to the inner setting Option if needed.

Create a static array for inner Setting for every primary Settings:

extension Option {static let generalValues: [InnerOptionValues] = [
.init(title: “About”, isAddSection: true, isUseToggle: false, headerTitle: “”),
.init(title: “Keyboard”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Fonts”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Language & Region”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Dictionary”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Reset”, isAddSection: true, isUseToggle: false, headerTitle: “”)
]

static let accessibilityValues: [InnerOptionValues] = [
.init(title: “Display & Text Size”, isAddSection: true, isUseToggle: false, headerTitle: “VISION”),
.init(title: “Motion”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Spoken Content”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Touch”, isAddSection: true, isUseToggle: false, headerTitle: “PHYSICAL AND MOTOR”),
.init(title: “Face ID & Attention”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Keyboards”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Subtitle & Captioning”, isAddSection: true, isUseToggle: false, headerTitle: “HEARING”)
]

static let privacyValues: [InnerOptionValues] = [
.init(title: “Location”, isAddSection: true, isUseToggle: true, headerTitle: “ENABLE TO ACCESS LOCATION”),
.init(title: “Contact”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Calander”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Remainders”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Photos”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Bluetooth”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Speech Recognition”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Camera”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Health”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “HomeKit”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Media & Apple Music”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Files & Folders”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Advertising”, isAddSection: true, isUseToggle: false, headerTitle: “”)
]

static let passwordAndAccountsValues: [InnerOptionValues] = [
.init(title: “Website and App Passwords”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “AutoFill Passwords”, isAddSection: false, isUseToggle: true, headerTitle: “”),
.init(title: “Add Account”, isAddSection: true, isUseToggle: false, headerTitle: “ACCOUNTS”),
.init(title: “Fetch New Data”, isAddSection: false, isUseToggle: false, headerTitle: “”)
]

static let mapsValues: [InnerOptionValues] = [
.init(title: “Siri & Search”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Notifications”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Background App Refresh”, isAddSection: true, isUseToggle: true, headerTitle: “ALLOW MAPS TO ACCESS”),
.init(title: “Driving”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Walking”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Transit”, isAddSection: true, isUseToggle: false, headerTitle: “”),
.init(title: “In Miles”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “In Kilometers”, isAddSection: false, isUseToggle: false, headerTitle: “”)
]

static let safariValues: [InnerOptionValues] = [
.init(title: “Siri & Search”, isAddSection: true, isUseToggle: false, headerTitle: “ALLOW SAFARI TO ACCESS”),
.init(title: “Search Engine”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Search Engine Suggestions”, isAddSection: false, isUseToggle: true, headerTitle: “”),
.init(title: “Safari Suggestions”, isAddSection: false, isUseToggle: true, headerTitle: “”),
.init(title: “Quick Website Search”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “AutoFill”, isAddSection: true, isUseToggle: false, headerTitle: “GENERAL”),
.init(title: “Frequently Visited Sites”, isAddSection: false, isUseToggle: true, headerTitle: “”),
.init(title: “Favorites”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Block Pop-ups”, isAddSection: false, isUseToggle: true, headerTitle: “”),
.init(title: “Show Link Previews”, isAddSection: false, isUseToggle: true, headerTitle: “”),
.init(title: “Downloads”, isAddSection: false, isUseToggle: false, headerTitle: “”)
]

static let newsValues: [InnerOptionValues] = [
.init(title: “Siri & Search”, isAddSection: true, isUseToggle: false, headerTitle: “ALLOW NEWS TO ACCESS”),
.init(title: “Background App Refresh”, isAddSection: false, isUseToggle: true, headerTitle: “”),
.init(title: “Show Story Previews”, isAddSection: true, isUseToggle: true, headerTitle: “NEWS SETTINGS”),
.init(title: “Restrict Stories in Today”, isAddSection: true, isUseToggle: true, headerTitle: “”),
.init(title: “Privacy”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Reset Identifier”, isAddSection: false, isUseToggle: true, headerTitle: “”),
.init(title: “Acknowledgements”, isAddSection: true, isUseToggle: false, headerTitle: “”)
]

static let developerValues: [InnerOptionValues] = [
.init(title: “DarkApperance”, isAddSection: true, isUseToggle: true, headerTitle: “APPERANCE”),
.init(title: “Enable UI Automation”, isAddSection: false, isUseToggle: true, headerTitle: “”),
.init(title: “Multipath Networking”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Fill Rate”, isAddSection: true, isUseToggle: false, headerTitle: “DEVELOPER APP TESTING”),
.init(title: “Ad Refersh Rate”, isAddSection: false, isUseToggle: false, headerTitle: “”),
.init(title: “Highlight Clipped Banners”, isAddSection: false, isUseToggle: true, headerTitle: “”),
.init(title: “Unlimited Ad Presentation”, isAddSection: false, isUseToggle: true, headerTitle: “”),
]
}

Create a static array for every primary Settings:

extension Option {static let options: [Option] = [
.init(id: 1, title: "General", isAddSection: false, values: generalValues),
.init(id: 2, title: "Accessibility", isAddSection: false, values: accessibilityValues),
.init(id: 3, title: "Privacy", isAddSection: false, values: privacyValues),
.init(id: 4, title: "Password & Accounts", isAddSection: true, values: passwordAndAccountsValues),
.init(id: 5, title: "Maps", isAddSection: false, values: mapsValues),
.init(id: 6, title: "Safari", isAddSection: false, values: safariValues),
.init(id: 7, title: "News", isAddSection: false, values: newsValues),
.init(id: 8, title: "Developer", isAddSection: true, values: developerValues)
]
}
  • We have first created a static array of inner settings values and used inner settings values in primary settings values.
  • Now, we have created a model class for Sample settings app.
  • We could also create another inner values of inner Settings but it would be complex to understand and explain.So I recreated two pages of Settings App.

2) Create a seperate View for SignIn, Bluetooth and WiFi

  • By using model class we have only implemented settings from General to Developer options.
  • But for SignIn, blutooth and WiFi I have created seperate View to explain about Picker,Toggle in detailed manner.

SignIn View:

SignInView.swift
  • In SignInView, We have used both VStack and HStack
  • Two Text are aligned in VStack and VStack is aligned horizontal to image using HStack

The resulting view we will get is:

SignIn View

Bluetooth View:

BluetoothView.swift
  • We have aligned Text and image in HStack and added navigation to the particular view and defined their properties in ToggleBluetoothView()

ToggleBluetoothView

ToggleBluetoothView.swift
  • We have used Form and Section in ToggleBluetoothView , to enable bluetooth ON and OFF.
  • Form— A container for grouping controls used for data entry, such as in settings or inspectors.
  • SwiftUI renders forms in a manner appropriate for the platform. For example, on iOS, forms appear as grouped lists. Use Section to group different parts of a form’s content.
  • Section — An affordance for creating hierarchical view content. Section will seperate a view from other views in Form.We can also give Header,Content and Footer to each Sections.
  • Toggle — A control that toggles between on and off states.
  • I have updated the toggle value of bluetooth in Settings Class .
  • ObjectBinding — A dynamic view property that subscribes to a bindable object automatically, invalidating the view when it changes.
Settings.swift
  • By using isBluetoothOn value I have shown ActivityIndicator to search for nearby devices.

ActivityIndicator

ActivityIndicator.swift
  • UIViewRepresentable — A view that represents a UIKit view.
  • UIViewType — The type of view to present(required for UIViewRepresentable)
  • uiView.startAnimating will make the activityIndicator appear when called.

After everything implemeted for BluetoothView, we will get view like this:

Bluetooth Setting

WiFi View:

For WiFiView, We have used Picker and to dispay picker we need to give picker values and picker values are declared in Settings.swift file.

Add picker values in Settings:

Settings.swift
  • types Pickervalues which is ON and OFF to enable WiFi.
  • type — Denotes the current picker value.

WiFIView:

WiFiView.swift

In WiFiView, we have used Group , Picker , ActivityIndicator, HStack

  • Group — An affordance for grouping view content.It will group all the view used inside.
  • Picker — A control for selecting from a set of mutually exclusive values.
  • Designing for picker label is declared in WifiContainer()
  • Based on selecting picker value , I have displayed activity indicator to search network.

WifiContainer View:

Picker Design - WifiContainer View
  • I have used Image and Text inside HStack to show Settings Option horizontally

After everything implemeted for WiFiView, we will get view like this:

WiFi View

3) Use SignIn,Bluetooth,WiFi view and rest of the view inside SettingsView

SettingsView.swift
  • We have NavigationView , Form, Section in Settings View.
  • By using static Primary Settings array , we have listed each setting in Form
  • Each Primary Settings View is listed by customizing them in using OptionRow()

OptionRow()

Added section to particular Primary Settings Viewin
  • In OptionRow, I have used Group to add particular Settings View in Section.
  • Settings View is customized and declared them in OptionSettingsView()

OptionSettingsView()

Customized Primary Settings View
  • NavigationButton is added to each view and navigate to inner settings view when view is clicked.
  • option.title — display title based on each array value.
  • Inner Settings for Primary Settings is used in OptionInnerDetail()

OptionInnerDetail()

Inner Settings View and View of its each Settings
  • In InnerOptionDetail() View, I have used Form to list each inner settings.
  • Each array is identified and pass the parameters of each inner settings array in OptionInnerView()
  • In OptionInnerView() View, I have used Group and inside grouping , I have added Section for View which needs to add Sections .
  • I have added Toggle for View which toggle need to be used and added both Section & Toggle where both needs to added.
  • Section header Text is also added to Inner Settings based on array values.
  • For Toggle, I have used ToggleView() or else I have used InnerView().

ToggleView()

ToggleView.swift
  • By using Settings class, I have used Toggle options based on isToggleOn value.
  • Toggle title and Image is used in HStack and title is used based on array values.
Updated Settings.swift File

InnerView()

Inner Settings View
  • As I mentioned above, I could have added another inner Settings Array but instead I have ended by displaying Simple text.
  • Same Customized View for primary Settings is also used for Inner Settings.
  • NavigationButton is added to Inner Settings View and When view is clicked , It will navigate to EndView()

EndView()

EndView.swift
  • When Inner Settings View is clicked, it will navigate to EndView() where it will display a coming soon text.
  • Every inner Settings will end here when clicked.

4) Conclusion

Now you can build and run the project in simulator iPhone XR, where you can see that We have recreated Settings App just like in iPhone XR.

Settings App

This project is updated for Xcode 11 and Swift 5.0.You can find this project in my github link.

I hope you found this article helpful. If you did, please don’t hesitate to clap as many times as you can or share this post on Twitter or your social media of choice, every share helps me to write more. If you have any queries, feel free to comment below and I’ll see what I can do.Thanks.

Let’s connect!

You can find me on Twitter | LinkedIn | GitHub

Developer in Making

Developer in Making is a mobile development blog that publishes articles and tutorials about the latest trends, techniques and new possibilities.

Shankar Madeshvaran

Written by

iOS and Xamarin Developer.I love to write articles regarding Techs,iOS and Xamarin concepts. Follow me on GitHub: https://github.com/shankarmadeshvaran

Developer in Making

Developer in Making is a mobile development blog that publishes articles and tutorials about the latest trends, techniques and new possibilities.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade