iOS 15 – How to get the best out of the SwiftUI app with less code – Part 1

iOS 15 – How to get the best out of the SwiftUI app with less code – Part 1

Share

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.


Share
No Comments

Sorry, the comment form is closed at this time.