
Blog
iOS 15 – How to get the best out of the SwiftUI app with less code – Part 1
Intro
In this blog post series, we will research the newly iOS15 opened-up API. It will be mostly about SwiftUI and how new modifiers can be applied to a project.
It’s always exciting when Apple announces a new iOS beta version. It brings a lot of changes and improvements. When it comes to SwiftUI, this was the best announcement so far. It contains a lot of the newly opened up API, which makes the developer’s life easier.
Before we start making an app, it’s important to fetch an API key from https://developer.nytimes.com/get-started as we’ll use their endpoint for fetching books. Set your API key in the ViesureBlogPostApp init method. Also, we will just put code snippets in this post, the fully working app can be cloned from our github repo.
List modifiers
One of the most important UI components is List. Apple didn’t disappoint us and a lot of methods for List customization have been opened up.
func getBooks() async { let books = await service.getBooks() DispatchQueue.main.async { self.books = books } }
Now that we have the async function that fetches the newest books, let’s use it with refreshable:
List { ... } .refreshable { await viewModel.getBooks() }
With this modifier, every time a user uses pull to refresh, the newest books will be fetched.
Let’s continue with a new great addition, swipeActions:
.swipeActions(edge: .leading) { Button(action: { book.bookmarked.toggle() }) { BookmarkImageView(isBookmarked: book.bookmarked) } .tint(book.bookmarked ? .yellow : .gray) }
Now we can create a wrapper around SwipeButton:
import SwiftUI struct SwipeableButton: View { @Binding var selection: Int? @Binding var book: Book var body: some View { HStack { Button(action: { selection = book.id }) { BookRowView(book: $book) .contentShape(Rectangle()) .background { NavigationLink(destination: BookDetailsView(book: $book), tag: book.id, selection: $selection) { EmptyView() } .hidden() } } .buttonStyle(.plain) } .swipeActions(edge: .leading) { Button(action: { book.bookmarked.toggle() }) { BookmarkImageView(isBookmarked: book.bookmarked) } .tint(book.bookmarked ? .yellow : .gray) } } }
Also the code rendering the list is updated, we are passing the reference to every book object as we would like to change the state of the bookmark directly on the object:
List { ForEach(viewModel.books.indices) { i in SwipeableButton(selection: $viewModel.selection, book: $viewModel.books[i]) } } .refreshable { await viewModel.getBooks() } .navigationTitle("Books")
The Code given above produces the next result:
One of the greatest additions to List is a searchable modifier. In only a few lines of code, an easy and user-friendly search option for a list array can be added. Firstly, we need to define the bindable(published) searchText:
@Published var searchText = ""
Then we can create a computed property that will filter the array based on the criteria or return all books if searchText is empty. As the filter is case-sensitive, we will compare lower-case strings:
var searchResults: [Book] { searchText.isEmpty ? books : books.filter{ $0.title.lowercased().contains(searchText.lowercased()) } }
At the end, we need to add a list modifier:
.searchable(text: $viewModel.searchText)
The other great thing about SwiftUI is that now we can add an asynchronous image in just a few lines. We will create a wrapper because we would like to have a loader as a placeholder all over the app where we use the async image:
import SwiftUI struct AsynchrounousImage: View { let url: URL let mode: ContentMode let cornerRadius: CGFloat init(url: URL, mode: ContentMode, cornerRadius: CGFloat = 0.0) { self.url = url self.mode = mode self.cornerRadius = cornerRadius } var body: some View { AsyncImage(url: url, content: { image in image.resizable() .aspectRatio(contentMode: mode) .cornerRadius(cornerRadius) }, placeholder: { ProgressView() }) } }
UI/UX overall
Also, a great addition to create a great UI is a Material ShapeStyles:
/// A material that's somewhat translucent. public static var regularMaterial: Material { get } /// A material that's more opaque than translucent. public static var thickMaterial: Material { get } /// A material that's more translucent than opaque. public static var thinMaterial: Material { get } /// A mostly translucent material. public static var ultraThinMaterial: Material { get } /// A mostly opaque material. public static var ultraThickMaterial: Material { get }
The usage is simple and can be used as a background:
.background(.ultraThinMaterial)
There is a long path in front of SwiftUI. If the development continues at this speed, it can become really mature in a near future. We look forward to the next update which can improve the maintainability of our app that runs with SwiftUI on production.