lade...

Kamil.id

Kamil.id

an avatar

Hello! Kamil

Follow @kamil on Micro.blog.

🌐 Visit Kamil.id 🌐 Kamil.id besuchen

Get Badge! Abzeichen holen! Write rieview Rezension schreiben News Neuigkeiten

Webfan Website Badge

Tags:

Rieviews

Bewertungen

not yet rated noch nicht bewertet 0%

Be the first
and write a rieview
about kamil.id.
Sein Sie der erste
und schreiben Sie eine Rezension
über kamil.id.

Kamil.id News

Reading Goals for 2025 📚

https://kamil.id/2025/01/01/read...

At the beginning of each year, I plan the books I want to read. This year, I decided to share my list publicly.

Few rules:

Since everything is clear now, let’s proceed with the list:

1.1.2025 22:18Reading Goals for 2025 📚
https://kamil.id/2025/01/01/read...

Isaac Asimov Foundation reading order by Tomasz Kołodziejczak 📚

https://kamil.id/2024/04/15/isaa...

Foundation book covers

Polish Fantasy/Fantastic Channel (Kanał Fantastyczny) - a YouTube profile about fantasy and science fiction published a great video where Tomasz Kołodziejczak (writer and long-time head of the Polish comic book department in Egmont publishing house) gives his perspective on Isaac Asimov Foundation series reading order. Since I had already read the Foundation book and wanted to explore this world more, I found it very useful, but a video form wasn’t good to dive into and check what book I should read next. In this article, you will find a condensed list extracted from this video (the video is in Polish), which I recommend you watch first:

Basic version

Proper version

Proper Plus version

Proper Plus + Prequels version

Cover image source

15.4.2024 17:23Isaac Asimov Foundation reading order by Tomasz Kołodziejczak 📚
https://kamil.id/2024/04/15/isaa...

How to add keyboard toolbar actions in SwiftUI

https://kamil.id/2021/09/11/how-...

How to add keyboard toolbar actions in SwiftUI

SwiftUI contains a clever mechanism for adding toolbar actions. With one view modifier - toolbar, we can control toolbar items in different places of the application. It doesn’t matter that you want to add a toolbar to the macOS navigation bar, iOS navigation bar, or iOS bottom bar; you will always start with .toolbar { }.

SwiftUI 3 brings additional, longly awaited functionality - the possibility to displays toolbar actions above the system keyboard. You probably expect that already, but adding views to the keyboard toolbar is also very easy. Let’s take a look at a provided example.

Toolbar over keyboard in SwiftUI

To show this functionality, I created a small project with the TextEditor and three keyboard actions - two of them put ASCI emojis in TextEditor, and the third one hides the keyboard.

struct ContentView: View {
    @State private var text = ""
    @FocusState private var focus: Bool
    
    var body: some View {
        TextEditor(text: $text)
            .focused($focus)
            .frame(height: 150)
            .padding()
            .overlay(
                RoundedRectangle(cornerRadius: 8)
                    .stroke(Color.primary, lineWidth: 1)
            )
            .padding()
    }
}

Code above displays TextEditor with some basic styling for better visibility. I also added the @FocusState property wrapper, which helps programmatically displaying and hiding the keyboard. The @FocusState is also a new addition in SwiftUI 3, but I’ll prepare a separate article about it.

.toolbar {
    ToolbarItemGroup(placement: .keyboard) {
        Button("Shrug") { text += "\n¯\\_(ツ)_/¯" }
        Button("Flip") { text += "\n(╯°□°)╯︵ ┻━┻" }
        Spacer()
        Button("Hide") { focus = false }
    }
}

I told you it would be simple. In the content of the toolbar view modifier, you have to put ToolbarItemGroup view with placement parameter set to keyboard. You can place any View in the content ViewBuilder of ToolbarItemGroup. Usually, you will want to use Buttons and Spacers to align them properly.

The first two buttons modify text property by adding correct ASCI characters; the third sets focus property to false. Calling this will hide the keyboard if it’s visible.

Below you can find the complete code of this experiment and a link to the Xcode project.

import SwiftUI

struct ContentView: View {
    @State private var text = ""
    @FocusState private var focus: Bool
    
    var body: some View {
        TextEditor(text: $text)
            .focused($focus)
            .frame(height: 150)
            .padding()
            .overlay(
                RoundedRectangle(cornerRadius: 8)
                    .stroke(Color.primary, lineWidth: 1)
            )
            .padding()
            .toolbar {
                ToolbarItemGroup(placement: .keyboard) {
                    Button("Shrug") { text += "\n¯\\_(ツ)_/¯" }
                    Button("Flip") { text += "\n(╯°□°)╯︵ ┻━┻" }
                    Spacer()
                    Button("Hide") { focus = false }
                }
            }
    }
}
Keyboard Toolbar Xcode Project

11.9.2021 12:06How to add keyboard toolbar actions in SwiftUI
https://kamil.id/2021/09/11/how-...

How to expand and collapse cells in SwiftUI

https://kamil.id/2021/08/08/how-...

How to expand and collapse cells in SwiftUI

Animations are a crucial but often neglected part of good UI. Correctly used can guide used in the app. In this article, you will learn how to make a list cell that expands on tap and apply animation to it.

We will create a simple Todo List with subtasks.

Models

First of all, let’s make a models for this data.

struct Task: Identifiable {
    let id: String = UUID().uuidString
    let title: String
    let subtask: [Subtask]
}

struct Subtask: Identifiable {
    let id: String = UUID().uuidString
    let title: String
}

Of course, it won’t be a fully working example. In the final application, you will probably need additional fields for selected state, deadline, etc.

Cells

Then let’s focus on cells. I decided to go with three separate cells designs. One for Subtask model:

struct SubtaskCell: View {
    let task: Subtask
    
    var body: some View {
        HStack {
            Image(systemName: "circle")
                .foregroundColor(Color.primary.opacity(0.2))
            Text(task.title)
        }
    }
}

one when a new/empty subtask:

struct EmptySubtaskCell: View {
    @State private var text: String = ""
    
    var body: some View {
        HStack {
            Image(systemName: "circle")
                .foregroundColor(Color.primary.opacity(0.2))
            TextField("new task", text: $text)
        }
    }
}

and one for a Task model:

struct TaskCell: View {
    @State private var isExpanded: Bool = false
    
    let task: Task
    
    var body: some View {
        content
            .padding(.leading)
            .frame(maxWidth: .infinity)
    }
    
    private var content: some View {
        VStack(alignment: .leading, spacing: 8) {
            header
            if isExpanded {
                Group {
                    ForEach(task.subtask) { subtask in
                        SubtaskCell(task: subtask)
                    }
                    EmptySubtaskCell()
                }
                .padding(.leading)
            }
            Divider()
        }
    }
    
    private var header: some View {
        HStack {
            Image(systemName: "square")
                .foregroundColor(Color.primary.opacity(0.2))
            Text(task.title)
        }
        .padding(.vertical, 4)
        .onTapGesture {
            withAnimation { isExpanded.toggle() }
        }
    }
}

That last one will support expanding and collapsing, so it’s a bit bigger. Let’s make a small dissection.

In the body, we load content and ensure that the cell takes the entire width of a given view.

var body: some View {
    content
        .padding(.leading)
        .frame(maxWidth: .infinity)
}

The TaskCell will store information about its state. You can find the isExpanded @State property wrapper on the top of it.

@State private var isExpanded: Bool = false

The cell’s content displays a header where the title and checkbox are displayed and subtasks if the cell is expanded. At the bottom, it also shows the Divider to separate one task from another.

private var content: some View {
    VStack(alignment: .leading, spacing: 8) {
        header
        if isExpanded {
            Group {
                ForEach(task.subtask) { subtask in
                    SubtaskCell(task: subtask)
                }
                EmptySubtaskCell()
            }
            .padding(.leading)
        }
        Divider()
    }
}

The cell’s header is similar to the SubtaskCell, but it displays a square image instead of a circle. It also has the onTapGesture method, which toggles the isExpanded state.

 private var header: some View {
    HStack {
        Image(systemName: "square")
            .foregroundColor(Color.primary.opacity(0.2))
        Text(task.title)
    }
    .padding(.vertical, 4)
    .onTapGesture { isExpanded.toggle() }
}

ToDo List

With all these cells, you are ready to create a final view.

struct TasksView: View {
    private let tasks: [Task] = [
        Task(title: "Create playground", subtask: []),
        Task(title: "Write article", subtask: []),
        Task(
            title: "Prepare assets",
            subtask: [
                Subtask(title: "Cover image"),
                Subtask(title: "Screenshots")
            ]
        ),
        Task(title: "Publish article", subtask: [])
    ]
    
    var body: some View {
        NavigationView {
            ScrollView {
                ForEach(tasks) { task in
                    TaskCell(task: task)
                        .animation(.default)
                }
                .navigationTitle("Todo List")
            }
        }
    }
}

At the top are some static Tasks hardcoded (for the production-ready application, you should store them in application state.)

Body list all tasks using the ForEach view. And how I achieved this animation effect, you may ask. It was as simple as adding the .animation(.default) view modifier to the TaskCell view.

Summary

By following this tutorial, you will achieve an effect presented at the top of the article. With the help of SwiftUI, adding animations that delights the user experience can be straightforward.

You can find the full code of this project below:

struct Task: Identifiable {
    let id: String = UUID().uuidString
    let title: String
    let subtask: [Subtask]
}

struct Subtask: Identifiable {
    let id: String = UUID().uuidString
    let title: String
}

struct TasksView: View {
    private let tasks: [Task] = [
        Task(title: "Create playground", subtask: []),
        Task(title: "Write article", subtask: []),
        Task(
            title: "Prepare assets",
            subtask: [
                Subtask(title: "Cover image"),
                Subtask(title: "Screenshots")
            ]
        ),
        Task(title: "Publish article", subtask: [])
    ]
    
    var body: some View {
        NavigationView {
            ScrollView {
                ForEach(tasks) { task in
                    TaskCell(task: task)
                        .animation(.default)
                }
                .navigationTitle("Todo List")
            }
        }
    }
}

struct TaskCell: View {
    @State private var isExpanded: Bool = false
    
    let task: Task
    
    var body: some View {
        content
            .padding(.leading)
            .frame(maxWidth: .infinity)
    }
    
    private var content: some View {
        VStack(alignment: .leading, spacing: 8) {
            header
            if isExpanded {
                Group {
                    ForEach(task.subtask) { subtask in
                        SubtaskCell(task: subtask)
                    }
                    EmptySubtaskCell()
                }
                .padding(.leading)
            }
            Divider()
        }
    }
    
    private var header: some View {
        HStack {
            Image(systemName: "square")
                .foregroundColor(Color.primary.opacity(0.2))
            Text(task.title)
        }
        .padding(.vertical, 4)
        .onTapGesture { isExpanded.toggle() }
    }
}

struct SubtaskCell: View {
    let task: Subtask
    
    var body: some View {
        HStack {
            Image(systemName: "circle")
                .foregroundColor(Color.primary.opacity(0.2))
            Text(task.title)
        }
    }
}

struct EmptySubtaskCell: View {
    @State private var text: String = ""
    
    var body: some View {
        HStack {
            Image(systemName: "circle")
                .foregroundColor(Color.primary.opacity(0.2))
            TextField("new task", text: $text)
        }
    }
}

8.8.2021 13:02How to expand and collapse cells in SwiftUI
https://kamil.id/2021/08/08/how-...

How to setup Code Climate Quality test coverage with Bitrise

https://kamil.id/2021/07/24/how-...

How to setup Code Climate Quality test coverage with Bitrise

Quality by Code Climate is a web service that gives you analytics for your code. It analyzes your code for code smells, and with proper CI/CD integration, it can track changes in test coverage data.

This article will list, step by step, how to set up Code Climate test coverage upload from Bitrise - probably most famous and widely used mobile Continues Integration and Continues Delivery service.

Initial assumption: Your GitHub repository is added to the Quality by Code Climate and the Bitrise.

#!/usr/bin/env bash

curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-darwin-amd64 > ./cc-test-reporter
chmod +x ./cc-test-reporter
./cc-test-reporter before-build
#!/usr/bin/env bash

xcrun xccov view --report $BITRISE_XCRESULT_PATH --json > coverage.json
./cc-test-reporter after-build --coverage-input-type xccov
Bitrise steps needed for test coverage setup

That’s it. With that few simple steps, you can send test coverage data from Bitrise to Code Climate.

24.7.2021 16:07How to setup Code Climate Quality test coverage with Bitrise
https://kamil.id/2021/07/24/how-...

How to dim Image for dark mode using ColorScheme

https://kamil.id/2021/07/11/how-...

How to dim Image for dark mode using ColorScheme

If you are using defaults colors for text and backgrounds, iOS/macOS handles dark mode literary by itself. But sometimes, you want to go with your style to make a brand more appealing or differentiate from the competition. Adjusting to dark mode required more work then, but still, the framework offers you many different options to make your life easier.

This article will explain how to use the ColorScheme environment variable to differentiate between light and dark mode and show overlay over the image when it is visible in the dark mode.

Let’s say that you are working on the sign-in screen for your application. You decided to use an image as a background to make it more appealing for users. But then you realize that you pick a photo with too vibrant colors for dark mode. There are few solutions for that.

Xcode project assets can support different assets for different modes. So you can edit the current image or find a different one that the application will use for a dark mode. But let’s say that you want to add new (usually big) assets to the project. What is the other option? You can use the ColorScheme environment variable to check the current mode and display a dark overlay above the image.

SwiftUI offers an Environment property wrapper to get environment variables. One of them, available by default under the colorScheme key, is a ColorScheme enum which can store two values, light and dark. To get that setting, add:

@Environment(\.colorScheme) private var colorScheme: ColorScheme

to your view.

Like nearly everything in SwiftUI, you can achieve a dim effect in many ways. I decided to go with ZStack and to display a rectangle with a translucent color.

if colorScheme == .dark {
    Rectangle()
        .fill(Color.black.opacity(0.3))
}

You can check the complete code example:

struct BackgroundView: View {
    @Environment(\.colorScheme) private var colorScheme: ColorScheme
    
    var body: some View {
        ZStack {
            Image("background")
                .resizable()
                .scaledToFill()
                .frame(minWidth: 0, maxWidth: .infinity)
            if colorScheme == .dark {
                Rectangle()
                    .fill(Color.black.opacity(0.3))
            }
            Text("Swift Wombat")
                .font(.largeTitle)
                .bold()
        }
        .ignoresSafeArea()
    }
}

The gallery below presents this effect in light and dark mode. As you can notice, the Swift Wombat title color was adjusted automatically.

Xcode Image dim in dark mode. Example project. Xcode Image dim in light mode. Example project.

11.7.2021 17:04How to dim Image for dark mode using ColorScheme
https://kamil.id/2021/07/11/how-...

How to add Pull to Refresh to the SwiftUI view

https://kamil.id/2021/06/27/how-...

How to add Pull to Refresh to the SwiftUI view

Pull to Refresh is a widespread UX pattern on mobile. Drag a list down and load new items. You can find that in all social media applications and almost all other apps that display data download from servers.

To achieve this in previous versions of SwiftUI, you will have to use UIKit code, create your view, or third-party library. But SWiftUI 3 brings a native ViewModifier that changes adding Pull to Refresh into child’s play. Let’s dive into code.

To display Pull to Refresh, you have to add a refreshable modifier to the scrollable view, most commonly List or Grid. The refreshable view modifier uses async/await syntax and handles dismissing the refresh indicator automatically.

struct ContentView: View {
    @State private var rows = 0

    var body: some View {
        List(0..<rows, id: \.self) { number in
            Text("row \(number)")
        }
        .refreshable {
            await Task.sleep(5_000_000_000) // wait 5s
            rows += 10
        }
    }
}

For this example, I’m using a Task.sleep that simply waits for a given number of nanoseconds to pass. Typically, you will perform time-consuming asynchronous (like downloading data from the network) operations here.

And that’s it. This code sample will add full Pull to Refresh handling. But there is one more thing that is worth mentioning. SwiftUI 3 added a new modifier to download initial data for a given view. This modifier is called task and also expects the async/await syntax.

struct ContentView: View {
    @State private var rows = 0

    var body: some View {
        List(0..<rows, id: \.self) { number in
            Text("row \(number)")
        }
        .task {
            await Task.sleep(3_000_000_000) // wait 3s
            rows = 10
        }
        .refreshable {
            await Task.sleep(5_000_000_000) // wait 5s
            rows += 10
        }
    }
}
Xcode refreshable experiment

27.6.2021 12:59How to add Pull to Refresh to the SwiftUI view
https://kamil.id/2021/06/27/how-...

How to display images from the web using AsyncImage

https://kamil.id/2021/06/12/how-...

How to display images from the web using AsyncImage

Each WWDC brings lots of new goods for all Apple ecosystem developers. The 2021 edition wasn’t different. AsyncImage view is a new SwiftUI addition that expects a URL instead of an asset to display images. Read further to learn how to use it.

Displaying images from the web is a widespread task in mobile applications. You can see this in all social media applications, web-based knowledge bases, or apps that stream files from servers. It requires more work than displaying images locally. After all, we have to wait until the image is loaded (usually displaying a placeholder) and handle situations where the network connection is not reliable (or the file doesn’t exist anymore). AsyncView is created to make those operations easier. When it comes to displaying images, it uses an Image view underneath.

The simplest form of AsyncView requires only the url parameter with the URL object. This url parameter is optional, so you don’t have to check its existence when creating it using the URL(string:_) constructor.

let url = URL(string: "http://placekitten.com/200/200")
AsyncImage(url: url)

But often, you may want to do more with a result. One of the most common scenarios was to resize the image to fit its frame. The AsyncImage first initializer can handle scale parameter and two convenient closures - content and placeholder. The first one performs operations on the Image view when it is finally loaded, and the second one returns a view displayed when an image is loading.

AsyncImage(url: url) { image in
    image.resizable()
} placeholder: {
    Text("Loading...")
}

With content closure, you can resize the image, fit it to content or apply rounded corners.

But there is a second initializer that allows for more advanced control. You will probably be using that version more often. It also supports url and scale properties, but there is a new transaction parameter and content closure gets AsyncImagePhase enum. You can use the data provided in that enum to return a view for different phases of image loading. When the network file is loaded (success phase), you will get an Image view. For failure error is returned. There is a third empty phase, which means that the file is being loaded. The transaction parameter is used to provide an animation that is applied when phases change.

AsyncImage(
    url: url,
    transaction: Transaction(
        animation: .easeInOut
    )
) { phase in
     if let image = phase.image {
         image.resizable()
    } else if phase.error != nil {
        Text("Error!")
    } else {
        Text("Loading...")
    }
}
AsyncImage SwiftUI Playground

12.6.2021 14:54How to display images from the web using AsyncImage
https://kamil.id/2021/06/12/how-...

How to change the Button view style

https://kamil.id/2021/05/30/how-...

How to change the Button view style

Button, as one of the most common UI elements, is used for versatile sets of actions. But actions can have a different level of importance. Sometimes you want to highlight the primary activity or mark the destructive one in red. You can do this by wrapping the Button view in a custom view, but SwiftUI creators design a better way by using buttonStyle(_:) view modifier.

The buttonStyle modifier accepts one argument, which is an object that implements the PrimitiveButtonStyle protocol. There are some predefined structs that you can use out of the box - DefaultButtonStyle, PlainButtonStyle, and BorderlessButtonStyle. To use them, call:

Button("Default button style", action: {})
    .buttonStyle(DefaultButtonStyle())
    
Button("Plain button style", action: {})
    .buttonStyle(PlainButtonStyle())
    
Button("Borderless button style", action: {})
    .buttonStyle(BorderlessButtonStyle())

You can look at the bottom of the article to find out how they look on iOS. But that styles adjust to the platform. They will look different on iOS, watchOS, and macOS.

Those are still minimal options. But you don’t have to stop here. Create your struct, implement PrimitiveButtonStyle protocol, which requires only one makeBody(configuration: Configuration) -> some View method, and you can create as many styles as you want.

The makeBody function is responsible for creating a button, so usually it w contains something like this Button(configuration), but you can modify the configuration or create a new one before you pass it to the Button view initializer.

In the example below, I created a button with a rounded border. Background color can be controller by the color property:

struct BorderedButtonStyle: PrimitiveButtonStyle {
    let color: Color

    func makeBody(configuration: Configuration) -> some View {
        ZStack {
            RoundedRectangle(cornerRadius: 16)
                .foregroundColor(color)
            Button(configuration)
                .foregroundColor(.white)
                .padding()
        }
    }
}

To apply the new style to the Button, use the same modifier as above:

Button("Bordered button style", action: {})
    .buttonStyle(BorderedButtonStyle(color: .orange))

Custom button styles can be a straightforward solution to prepare a tailored set of button styles dedicated to your app.

Xcode playground to test different button styles

30.5.2021 18:06How to change the Button view style
https://kamil.id/2021/05/30/how-...

How to use PersonNameComponentsFormatter

https://kamil.id/2021/05/23/how-...

How to use PersonNameComponentsFormatter

Foundation framework available for iOS, macOS, iPadOS, and other Apple platforms hides many gems that you can use to simplify operations required in nearly every application. One of the very often used sets of features are formatters. You probably are familiar with DateFormatters, but in today’s article, I’ll describe how to use PersonNameComponentsFormatter, which you can use to parse and format a person’s name.

The structure of personal names varies between different locales. On the other hand, there are significant and meaningful for persons that use them. That’s a reason you should treat them with respect and very carefully. PersonNameComponentsFormatter can help you with that. This formatter can parse string data provided and separated them into PersonNameComponents, and get that structure to display name using the correct locale.

First, you have to create an instance of the PersonNameComponentsFormatter:

let formatter = PersonNameComponentsFormatter()

To separate name string for components use personNameComponents(from:) function:

if let components = formatter.personNameComponents(
    from: "Sir David Frederick Attenborough"
) {
    print(components)
    /* Prints:
       namePrefix: Sir
       givenName: David
       middleName: Frederick
       familyName: Attenborough
    */
}

If you already have components (for example, created from the form data), you can use them with the same class to build different data representations. You can use one of 4 styles short, medium(default), long, abbreviated.

formatter.style = .default
print(formatter.string(from: components))
// David Attenborough

formatter.style = .abbreviated
print(formatter.string(from: components))
// DA

formatter.style = .long
print(formatter.string(from: components))
// Sir David Frederick Attenborough

formatter.style = .medium
print(formatter.string(from: components))
// David Attenborough

formatter.style = .short
print(formatter.string(from: components))
// David

Additionally, PersonNameComponentsFormatter can prepare an Attributed String.

As you can see, PersonNameComponentsFormatter is very easy to use. With correctly collected or parsed data, you will create correct abbreviations or long-style formatted person details.

PersonNameComponentsFormatter Xcode Playground

23.5.2021 13:44How to use PersonNameComponentsFormatter
https://kamil.id/2021/05/23/how-...

How to control Image view interpolation in SwiftUI

https://kamil.id/2021/05/09/how-...

How to control Image view interpolation in SwiftUI

When you are scaling up (and down) a small image in SwiftUI, there is an operation called interpolation. The size of a current image pixel is calculated based on neighbor pixels. It gave a more natural and better-looking effect, but it also means that the image appears blurry (look at the first image in the example above). More advanced interpolations also increase computational complexity.

Sometimes, you want to display the image in a more pixelated form (second image in the example above). There is a view modifier for that. Its name is an interpolation(...) (surprise, surprise).

Top: Interpolation - default, Bottom: Interpolation - none

Top: Interpolation - default, Bottom: Interpolation - none

Using this modifier is very easy. It expects one of four Interpolation enum values: high, medium, low, and none. The first three will control interpolation quality, and the last one goes with the most straightforward approach of duplicating current pixel values (and gives pixelated effect).

To change interpolation quality, call the given view modifier on Image view:

Image("wombat")
    .interpolation(.none)

You will probably not need that function too often, but it’s good to know its existence and play with that setting a bit.

Image interpolation Xcode playground

9.5.2021 13:16How to control Image view interpolation in SwiftUI
https://kamil.id/2021/05/09/how-...

How to display alerts in SwiftUI

https://kamil.id/2021/05/03/how-...

How to display alerts in SwiftUI

If you are an iOS or macOS user, you have seen alerts many times. Alerts are components that inform you about some errors or ask about some destructive actions. It would be best if you were careful not to overuse them in your app because it may distract users.

iOS Alert Example

SwiftUI offers a structure that makes displaying alerts very easy, but you must remember two limitations:

  1. Due to the nature of SwiftUI, you must specify your alerts as an element of the view’s tree. You will expect that displaying an alert should be a function, but it will be imperative programming. In the declarative paradigm, you specify the whole interface before the app starts. Messages displayed to users must also conform to that rule.

  2. You will add alerts as view modifiers. If you add two of them at the same level, the second one will override the first. In that case, it’s better to create an enum that will combine all alerts that you want to present or, as you will see in the example below, add alerts somewhere lower the view’s tree.

With that two limitations in mind, you can go to the fun part - implementation. Alerts are added to the view’s tree at the beginning, so they need some Binding, usually a @State that will tell they should be displayed or not.

Simple Alert View Modifier

There are two versions of the alert(...) view modifier. The first one expects a boolean value that represents isPresented state of alert. You modify your state, setting it true to display it.

struct SimpleAlert: View {
    @State private var showAlert = false

    var body: some View {
        Button("Show alert") {
            showAlert = true
        }
        .alert(isPresented: $showAlert) {
            // Alert view
        }
    }
}

One thing is worth noticing. You need to use $ before the name of your @State variable to get Binding<Bool> from it.

The content block of the alert(..) modifier expects an Alert view. Its initializers look like this:

public init(
    title: Text,
    message: Text? = nil,
    dismissButton: Alert.Button? = nil
)
public init(
    title: Text,
    message: Text? = nil,
    primaryButton: Alert.Button,
    secondaryButton: Alert.Button
)

You can use the first one for simple confirmation messages (like error or info messages). Only title Text view is required for this one. By default, it displays an OK button.

The second one gives you more freedom. You must set a primaryButton and a secondaryButton for it. You can select from cancel(...), default(...) and destructive(...) button styles. System calls primary button when the view is dismissed, so watch out to not set any destructive action there.

Alert(
    title: Text("Uninstall this application"),
    message: Text("Do you want to uninstall this application? This action cannot be undone."),
    primaryButton: .cancel(),
    secondaryButton: .destructive(
        Text("Uninstall"),
        action: { /* do something */ }
    )
)

You don’t have to worry about setting showAlert to false again. SwiftUI will handle this for you. The cancel button is empty in this example.

Full code to display simple alert look like this:

struct SimpleAlert: View {
    @State private var showAlert = false

    var body: some View {
        Button("Show alert") {
            showAlert = true
        }
        .alert(isPresented: $showAlert) {
            Alert(
                title: Text("Uninstall this application"),
                message: Text("Do you want to uninstall this application? This action cannot be undone."),
                primaryButton: .cancel(),
                secondaryButton: .destructive(
                    Text("Uninstall"),
                    action: { /* do something */ }
                )
            )
        }
    }
}

Advance Alert View Modifier

The second version of alert(...) can be used when the Alert state depends on the property bound to it. Instead of isPresented, you provide binding to an optional item - a struct, class, or enum that conforms to Identifiable protocol.

enum Animal: String, CaseIterable, Identifiable {
    case wombat, koala, platypus

    var id: String { rawValue }
}

struct AnimalPicker: View {
    @State private var selectedAnimal: Animal?

    var body: some View {
        VStack {
            Text("Please select an animal:")
            button(for: .wombat)
            button(for: .koala)
            button(for: .platypus)
        }
        .alert(item: $selectedAnimal) { animal in
            Alert(
                title: Text("Selected animal:"),
                message: Text(animal.rawValue.capitalized)
            )
        }
    }

    private func button(for animal: Animal) -> some View {
        Button(animal.rawValue.capitalized) {
            selectedAnimal = animal
        }
    }
}

Content block of that version of the view modifier the same as the previous one expects Alert view, but it provides you a selected item. When the selected item is nil alert is not displayed. But when you set it to an existing object, the Alert view will show. When the message is dismissed, the selected item is set back to nil.

Xcode Playground where you can experiment with Alert views

3.5.2021 11:19How to display alerts in SwiftUI
https://kamil.id/2021/05/03/how-...

How to synchronize View and Model with SwiftUI's EnvironmentObject

https://kamil.id/2021/04/25/how-...

How to synchronize View and Model with SwiftUI's EnvironmentObject

SwiftUI offers an easy way to inject your model classes into a view hierarchy. Additionally, it handles model changes pretty automatically, and only a tiny amount of additional code is needed from your side. In this article, I’ll explain how to provide and retrieve @EnvironmentObject anywhere within your views hierarchy.

Why do you need an environment object, you may ask? It simplifies your code. Of course, you can create a @State or @StateObject in your top view and provide it as Binding down below, but this will make your code less modular. Think of environment objects like a dependency injection mechanism. You provide your model in one place, and then in another, you can read it or perform actions. My favorite Redux-like state management library SwiftDux uses environment objects to provide store and action dispatcher to any view down the graph.

The first puzzle you need is a class (model) that you will pass as an environment object. It needs to conform to the ObservableObject protocol. In that class, you can use the @Published property wrapper to expose a variable. When you change the value of the @Published variable, it will inform ObservableObject, and SwiftUI will handle the rest. Of course, a given object can contain standard variables and methods as well.

class Counter: ObservableObject {
    @Published var value = 0
}

Now, when you have your model ready, you must find a place to create an instance of it and inject it into the SwiftUI view structure. Usually, this is the first view of the view’s hierarchy. But it doesn’t have to be. Sometimes, you want to provide some data to just a part of a structure. When you pick your place, create an instance of your model class using the @StateObject property wrapper, and then inject it using the environmentObject view modifier

struct ContentView: View {
    @StateObject private var counter = Counter()
    
    var body: some View {
        CounterView()
            .environmentObject(counter)
    }
}

You created your model, provided it for SwiftUI views, so it’s time to learn how to use it. To retrieve your model, you should use the @EnvironmentObject property wrapper. You can operate on the provided object, and SwiftUI will refresh it in other places of the UI where the given environment object is used.

struct CounterView: View {
    @EnvironmentObject private var counter: Counter

    var body: some View {
        VStack {
            Text("\(counter.value)")
            Divider()
            IncrementView()
        }
    }
}
struct IncrementView: View {
    @EnvironmentObject private var counter: Counter

    var body: some View {
        Button("Increment", action: { counter.value += 1 })
    }
}
Xcode Playground with EnvironmentObject example

25.4.2021 11:42How to synchronize View and Model with SwiftUI's EnvironmentObject
https://kamil.id/2021/04/25/how-...

How to format Swift source code using SwiftFormat

https://kamil.id/2021/04/11/how-...

How to format Swift source code using SwiftFormat

Command + I is a very commonly used Xcode shortcut. It is used to fix the indentation of the selected code part. But it does no more than that. What’s worst, it adds four spaces (or one tab if you use it for indentation) to empty lines. This article will learn how to install, configure and use a third-party command-line application called SwiftFormat for better Swift source code formatting.

SwiftFormat is an open-source project created and maintained by Nick Lockwood - creator of a few famous and commonly used iOS libraries. It formats your code using more than 50 rules, which you can turn off and turn on individually. If you follow just the basic code formatting principles, you can be sure that SwiftFormat will handle the rest, giving you well-formatted code.

You can install SwiftFormat using one of the provided methods. I’ll describe only one that is easiest for me - by using Homebrew.

You have to first install Homebrew and then call

brew install swiftformat

And that’s it. SwiftFormat is installed and ready to use.

To run SwiftFormat over your project, go into the console to the project catalog and call:

swiftformat .

The presented command will format all .swift files into this and all subdirectories. For the first run, I advise you to commit or stash your current code changes.

If you go to documentation, you will find that you can control different properties. You can specify them using the command line argument of the .swiftformat file placed in the same director.

I like default settings, so the only thing I do is turn off formatting on some generated directories (like CoreData or Pods) and directly specify the Swift version. I made the swiftformat.sh file for this that looks similar to this one:

#!/bin/bash
# Install swiftformat via brew first with `brew install swiftformat`

swiftformat . --swiftversion 5.3 --exclude Shared/Resources,Shared/Models/CoreData --disable wrapMultilineStatementBraces

11.4.2021 12:04How to format Swift source code using SwiftFormat
https://kamil.id/2021/04/11/how-...

How to create your own property wrapper

https://kamil.id/2021/04/05/how-...

How to create own property wrapper

Swift (SwiftUI in particular) commonly uses property wrappers to make repeatable operations easier. For example, the @State is used for observing and storing view state, and @AppStorage to store app parameters using local persistence.

Learn how to use AppStorage to access UserDefaults in SwiftUI.

But you are not limited to build-in property wrappers. You can make your property wrappers and add additional functionalities to struct and class properties (property wrappers are not supported at the top level).

This article will learn how to build a super simple property wrapper that will print the current value when you read or write it. It can help debug, but the goal is to show you how custom property wrappers work.

Property wrapper is a struct marked with @propertyWrapper attribute. It requires a wrappedValue property.

@propertyWrapper
struct CustomWrapper<Value> {
    var wrappedValue: Value
}

That’s it. You can now use your property wrapper on any property.

@CustomWrapper var value = 0

But in the current state, it doesn’t do anything. Let’s add some logic when the wrapped value is set or get.

@propertyWrapper
struct Logged<Value> {
    private var value: Value

    init(wrappedValue: Value) {
        self.value = wrappedValue
    }

    var wrappedValue: Value {
        get {
            print("Value get: \(value)")
            return value
        }
        set {
            print("Value set: \(newValue)")
            value = newValue
        }
    }
}

When you try to read or change a given property, you will also see information about a current state on the console.

// Property wrappers are not supported on top level
struct Object {
    @Logged var value = 0
}

var object = Object()

print("Value is equal to: \(object.value)")
print("---")
object.value = 100
print("---")
print("Value is equal to: \(object.value)")

/*
Prints:
Value get: 0
Value is equal to: 0
---
Value set: 100
---
Value get: 100
Value is equal to: 100
*/

Of course, the provided example is elementary, but property wrappers can be used in various cases. Some libraries help with data validation and simplify usage of codable.

Simple property wrapper example - Xcode Playground

5.4.2021 13:19How to create your own property wrapper
https://kamil.id/2021/04/05/how-...

How to disable button in SwiftUI

https://kamil.id/2021/03/28/how-...

How to disable button in SwiftUI

In your SwiftUI applications, you will often use the Button view. This interactive control is used to build simple user inputs. But in this article, you will learn how to disable buttons.

Think about a situation as follow - you have three different options, but some of them are only active when the toggle is true. You can hide inactive options using the if statement, but this will be counterintuitive for users. The “Don’t make me think” principle assumes that users should know what’s going on and not be surprised by UI. You can achieve it by just disabling that options.

To disable a button, you should use the disabled view modifier. It takes a bool parameter, which, for example, can be taken from the state.

@State private var disabled = true

var body: some View {
    Button("Press me", action: {})
        .disabled(disabled)
}

You can use the disabled modifier on a view above buttons (VStack, for example). That way, you will apply it to all buttons (or other controls below).

VStack {
    Button("Press me 1", action: {})
    Button("Press me 2", action: {})
    Button("Press me 3", action: {})
}
.disabled(true)

Note: According to the documentation, disabled from a view higher in scope takes precedence over disabled from lower views. But my experiments (you can find a link to the project below) show that priority has one that its parameter is set to true and order is not important.

Disable button Xcode Example

28.3.2021 09:56How to disable button in SwiftUI
https://kamil.id/2021/03/28/how-...

How to display a scrollable list using the List view

https://kamil.id/2021/03/13/how-...

How to display a scrollable list using the List view

List view is a container that can present a scrollable list of views in SwiftUI. You can adjust its look, and it supports versatile actions and change its style depending on the platform. This article will learn how to use the List to display navigation links, data from an array and use ForEach and Section to display multi-dimensional data structures.

Simple List

Simple list

As always, SwiftUI creators made it easy to use API. You can display a basic List with only a few lines of code.

List {
    Text("Identifiable List")
    Text("Multi-Dimensional List")
    Text("Inset Grouped List Style")
}

Additionally, wrapping your items in List instead of VStack will make them scrollable. So, if there are more items that fit on the screen, they will still be accessible to users.

You can use the List view to build the app navigation. When you wrap your Texts (or any other View) in NavigationLink, they will get an additional details arrow and, together with NavigationView, will handle switching between screens.

NavigationView {
    List {
        NavigationLink(destination: IdentifiableList()) {
            Text("Identifiable List")
        }
        NavigationLink(
            destination: MultiDimensionalList()
                 .navigationTitle("Multi-Dimensional List")
        ) {
            Text("Multi-Dimensional List")
        }
        NavigationLink(
            destination: MultiDimensionalList()
                .listStyle(InsetGroupedListStyle())
                .navigationTitle("Inset Grouped Style")
        ) {
            Text("Inset Grouped List Style")
        }
    }
    .navigationTitle("Simple list")
}

Identifiable List

Identifiable list

When using lists, you usually have an array of data. One of the List initializers can get that data and generates a list of items. It will handle rendering only visible entities at a given moment which is essential for application memory usage.

The data that you provide must conform to the Identifiable protocol. It’s straightforward to achieve. Your struct must have an id property that conforms to Hashable. String or UUID type is usually perfect for this.

struct Continent: Identifiable {
    let id: UUID = UUID()
    let name: String
}

private let continents = [
    Continent(name: "Asia"),
    Continent(name: "Africa"),
    Continent(name: "North America"),
    Continent(name: "South America"),
    Continent(name: "Antarctica"),
    Continent(name: "Europe"),
    Continent(name: "Australia")
]

With struct declared like the one above, you can easily display an array of structs with just three lines:

List(continents) {
    Text($0.name)
}

Multi-dimensional List

Multi-Dimensional List

With the support of two other views - ForEach and Section you can create a stunning two-dimensional list. For this example, I added an array of animals to a Continent struct.

struct Continent: Identifiable {
    let id: UUID = UUID()
    let name: String
    let animals: [Animal]
}

struct Animal: Identifiable {
    let id: UUID = UUID()
    let name: String
}

When you build your example structure, what is only need to iterate over items. The Section view displays a header, and objects in each behave like normal list cells.

List {
    ForEach(continents) { continent in
        Section(header: Text(continent.name)) {
            ForEach(continent.animals) { animal in
                Text(animal.name)
            }
        }
    }
}

Inset Grouped List Style

Multi-Dimensional List

This article covers just an iceberg of list features, but I want to mention one more element that, with just one line, will take your design to the next level. I’m talking about the listStyle modifier. With one line:

.listStyle(InsetGroupedListStyle())

You may change a list from the previous section to the one visible at the start of this one.

Xcode Project to test presented examples

13.3.2021 21:08How to display a scrollable list using the List view
https://kamil.id/2021/03/13/how-...

How to display UIKit view in SwiftUI using UIViewRepresentable

https://kamil.id/2021/02/28/how-...

How to display UIKit view in SwiftUI using UIViewRepresentable

One of the biggest arguments in favor of using SwiftUI in production apps is the interoperability of UIKit. If you have a problem with achieving something with pure SwiftUI, you can always use UIViewRepresentable to wrap UIKit view and put it in the SwiftUI app only in dozen lines of code. How to do this? Read further.

This article will teach you how to wrap UITextView into UIViewRepresentable protocol, with the Coordinator notifying your app when the text changes.

I used an elementary example with UITextView, but creating UIViewRepresentable will always look very similar. First, you have to wrap your view in the protocol and then build a Coordinator to handle UIKit delegate and data source methods.

UIViewRepresentable

UIViewRepresentable protocol requires confirmation for two methods.

associatedtype UIViewType: UIView
func makeUIView(context: Self.Context) -> Self.UIViewType
func updateUIView(_ uiView: Self.UIViewType, context: Self.Context)

The makeUIView method should create a UIKit view and returns it for SwiftUI to handle. SwiftUI calls this method when the view is created. Then updateUIView is called when your view should be updated. Both methods bring you a UIViewRepresentableContext struct which you can use to get Coordinator or various environment values.

A simple implementation for UITextView may look like this:

struct TextView: UIViewRepresentable {
    func makeUIView(context: Context) -> UITextView {
        let view = UITextView()
        view.layer.borderWidth = 1
        view.layer.borderColor = UIColor.systemGray.cgColor
        view.layer.cornerRadius = 8
        return view
    }

    func updateUIView(_ uiView: UITextView, context: Context) {
        
    }
}

When used in SwiftUI, the code above will display a UITextView with a border for better visibility.

But you may want to provide the initial text value and refresh text if it changes from outside. For this, you should use a Binding property wrapper and updateUIView.

struct TextView: UIViewRepresentable {
    @Binding var text: String // NEW

    func makeUIView(context: Context) -> UITextView {
        let view = UITextView()
        view.layer.borderWidth = 1
        view.layer.borderColor = UIColor.systemGray.cgColor
        view.layer.cornerRadius = 8
        return view
    }

    func updateUIView(_ uiView: UITextView, context: Context) {
        uiView.text = text // NEW
    }
}

You can use this view in SwiftUI as follow:

struct ContentView: View {
    @State private var text: String = "Initial text"
    
    var body: some View {
        TextView(text: $text)
    }
}

Coordinator

What you saw is just half of the equation. You will also need to know when UITextView content did changed. For this, UIViewRepresentable offers an optional method:

 associatedtype Coordinator = Void
 func makeCoordinator() -> Self.Coordinator

that you can implement and along with Binding property used to inform SwiftUI about current text UITextView.

First, start with the makeCoordinator method. This method can return any entity, no matter struct or class.

func makeCoordinator() -> Coordinator {
    Coordinator(text: $text)
}

Your Coordinator object will receive the text variable as a Binding (returned with $ syntactic sugar).

Ok, now you can go to Coordinator class implementation.

class Coordinator: NSObject, UITextViewDelegate {
    @Binding private var text: String

    init(text: Binding<String>) {
        self._text = text
    }

    func textViewDidChange(_ textView: UITextView) {
        text = textView.text
    }
}

As you can see, it implements UITextViewDelegate protocol (and NSObject required by it), and in textViewDidChange, it updates the text variable. In this example, the Coordinator is a class, so it needs an init method that sets Binding<String> to local property wrapper using _text syntax.

There is one puzzle missing. You have to connect your Coordinator with UITextView. Do this in makeUIView, but don’t create it there. Use the one provided in context.

func makeUIView(context: Context) -> UITextView {
    let view = UITextView()
    view.delegate = context.coordinator // NEW
    view.layer.borderWidth = 1
    view.layer.borderColor = UIColor.systemGray.cgColor
    view.layer.cornerRadius = 8
    return view
}

Wrap up

As you can see, using UIKit views in SwiftUI is easy. You can check the complete code, along with an example of how to use it with the State property wrapper below.

struct ContentView: View {
    @State private var text: String = "Initial text"
    
    var body: some View {
        TextView(text: $text)
    }
}

struct TextView: UIViewRepresentable {
    @Binding var text: String

    func makeUIView(context: Context) -> UITextView {
        let view = UITextView()
        view.delegate = context.coordinator
        view.layer.borderWidth = 1
        view.layer.borderColor = UIColor.systemGray.cgColor
        view.layer.cornerRadius = 8
        return view
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(text: $text)
    }

    func updateUIView(_ uiView: UITextView, context: Context) {
        uiView.text = text
    }

    class Coordinator: NSObject, UITextViewDelegate {
        @Binding private var text: String

        init(text: Binding<String>) {
            self._text = text
        }

        func textViewDidChange(_ textView: UITextView) {
            text = textView.text
        }
    }
}
UIViewRepresentable Xcode example project

28.2.2021 21:16How to display UIKit view in SwiftUI using UIViewRepresentable
https://kamil.id/2021/02/28/how-...

How to add rounded corners in SwiftUI

https://kamil.id/2021/02/21/how-...

How to add rounded corners in SwiftUI

Rounded corners, like many other things in SwiftUI, can be added in few different ways. In this article, you will learn the simples one and one more sophisticated that you may find helpful in some circumstances. Let’s start.

ViewModifier

cornerRadius modifier effect

The easiest way to add rounded corners to any view is by using the cornerRadius modifier. Of course, you have to add background to see the effect of this modifier. You can achieve the example above with these five lines of code:

Text("Swift Wombat")
    .font(.largeTitle)
    .padding()
    .background(Color.green)
    .cornerRadius(16)

The cornerRadius modifier supports two parameters. The first unnamed one is radius, and the second one is antialiased, which is by default set to true.

Shape

RoundedRectangle effect

Instead of using cornerRadius, you can create a more sophisticated view by using ZStack.

I used a similar solution to create chat bubbles.

A solution like this isn’t probably recommended to add only a background with rounded corners, but you may find it useful to start playing with advanced views. This solution begins with the RoundedRectangle shape. Since the Shape struct is also a View, you can use it as a base for your creations. Putting RoundedRectangle into ZStack, just above your given view, will create a rounded background effect.

ZStack {
    RoundedRectangle(cornerRadius: 16)
        .foregroundColor(.green)
    Text("Swift Wombat")
        .font(.largeTitle)
        .padding()
}

When creating the RoundedRectangle struct, you can provide a cornerRadius parameter.

Overlay

strokeBorder and RoundedRectangle effect

There is also a third solution, often used to create borders. This solution uses the overlay to display a view above another view. If you use it for the rounded background, it will cover your creation. But it will work perfectly for borders.

    Text("Swift Wombat")
        .font(.largeTitle)
        .padding()
        .overlay(
            RoundedRectangle(cornerRadius: 16)
                .strokeBorder()
                .foregroundColor(.green)
        )
Xcode Playground with rounded corners examples

21.2.2021 12:12How to add rounded corners in SwiftUI
https://kamil.id/2021/02/21/how-...

How to style Text view in SwiftUI

https://kamil.id/2021/02/15/how-...

How to style Text view in SwiftUI

You can style SwiftUI Text views with a bunch of dedicated ViewModifiers. Learn about them in this article.

Text styles covered in this article

How to apply italic text style?

You can switch the font to cursive using the italic modifier.

Text("italic")
    .italic()

How to apply bold text style?

The bold view modifier will make the displayed text bolder.

Text("bold")
    .bold()

Of course, you can stack these two modifiers to create an italic and bold typeface.

Text("bold & italic")
    .bold()
    .italic()

How to change font weight?

If you want to set your font to something different than bold, you can use the weight modifier.

Text("fontWeight heavy")
    .fontWeight(.heavy)

How to change the font?

The font modifier is applicable if you want to change more text parameters. There are predefined fonts that you can use for various elements of the app. In the example below title font is set.

Text("font .title")
    .font(.title)

You can create own Font struct to pick different font family, size or weight.

How to strikethrough a text?

There are two versions of the strikethrough modifier. One without argument that displays a line in the foreground color:

Text("strikethrough")
    .strikethrough()

The second one has an active parameter and color option where you can control a strikethrough color.

Text("strikethrough color")
    .strikethrough(true, color: .red)

How to underline a text?

Instead of strikethrough, you may want to underline a text. API for this is very similar. More straightforward underline modifier takes foreground color value:

Text("underline")
    .underline()

And more advanced has active and color parameters:

Text("underline color")
    .underline(true, color: .red)

How to change the text color?

Text view content color can be changed using the foregroundColor modifier.

Text("foregroundColor")
    .foregroundColor(.red)

Learn how to colorize text using a gradient.

How to change the Text view background color?

The background of any view can be applied using the background modifier. Since SwiftUI Color is also a view, you can use it to apply a color background to the text below.

Text("background")
    .background(Color.red)

How to change the baseline offset of text?

The baseline can be used to display your text above or below the normal text baseline. The baselineOffset view modifier accept positive and negative numbers. You will often use it with Text view concatenation with + sign.

Text("baseline ")
   + Text("Off")
       .baselineOffset(10)
   + Text("set")
       .baselineOffset(-10)

How to adjust kerning?

You can adjust kerning (the distance between a pair of letters) with the kerning modifier.

Text("kerning kerning kerning")
    .kerning(1.2)

How to adjust tracking?

Tracking delivers a similar effect to kerning, but it adds distance between each character, which means that ligatures will also be separated.

Text("tracking tracking tracking")
    .tracking(1.2)
Text styles examples in Xcode Playground

15.2.2021 16:27How to style Text view in SwiftUI
https://kamil.id/2021/02/15/how-...

How to display swipeable pages in SwiftUI

https://kamil.id/2021/02/05/how-...

How to display swipeable pages in SwiftUI

Swipeable pages, often used for application onboardings, can be easily made using SwiftUI TabView. In this tutorial, you will learn how to create a view like the one presented in the (low-quality) GIF below.

Slideable pages preview animation

When you see TabView's name, the first you may think is a tab bar navigation like the one you can make using UITabBarController. It is its first and most common usage, but TabView can be styled to look like UIPageViewController.

TabView has a straightforward API. Basic usage of it looks like this:

TabView {
    Text("Page 1")
    Text("Page 2")
    Text("Page 3")
}

You list all tabs/pages using view builder. It’s often used with a specification of images and titles placed on the tab bar items:

TabView {
    Text("Page 1")
        .tabItem {
            Image(systemName: "1.square.fill")
            Text("Page 1")
        }
    Text("Page 2")
        .tabItem {
            Image(systemName: "2.square.fill")
            Text("Page 2")
        }
    Text("Page 3")
        .tabItem {
            Image(systemName: "3.square.fill")
            Text("Page 3")
        }
}

To change the tab bar to page view, you have to set a different style using tabViewStyle modifier:

.tabViewStyle(PageTabViewStyle())

To achieve the effect from GIF, you have to create a Page view first:

import SwiftUI

struct Page: View {
    let title: String
    let image: String
    let color: Color
    
    var body: some View {
        VStack {
            Image(image)
                .resizable()
                .scaledToFit()
                .padding()
            Text(title)
                .bold()
                .font(.title)
                .foregroundColor(.white)
                .padding(.bottom, 32)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(color)
        .cornerRadius(16)
        .padding()
    }
}

and then configure TabView as follow:

import SwiftUI

struct ContentView: View {
    var body: some View {
        TabView {
            Page(
                title: "Wombat",
                image: "wombat",
                color: Color(hex: 0xF8CF3C)
            )
            Page(
                title: "Koala",
                image: "koala",
                color: Color(hex: 0xF6B6AC)
            )
            Page(
                title: "Platypus",
                image: "platypus",
                color: Color(hex: 0xF26E64)
            )
        }
        .tabViewStyle(PageTabViewStyle())
    }
}
Swipeable pages Xcode project template

5.2.2021 20:00How to display swipeable pages in SwiftUI
https://kamil.id/2021/02/05/how-...

How to use ZStack to create a chat bubble

https://kamil.id/2021/01/31/how-...

How to use ZStack to create a chat bubble

ZStack is one of the basic building blocks of SwiftUI applications. Along with HStack and VStack, you will use them frequently. This article will explain what ZStack is and how to use it to build a crucial part of every chat UI - a chat bubble.

Chat bubbles example

So what is this view with the strange name ZStack? It’s a view that arranges its children one into another/overlays them. In 3D space, we usually mark the third direction z along x and y.

ZStack syntax is very similar to HStack and VStack.

ZStack {
    Circle()
        .foregroundColor(.green)
    Text("Swift Wombat")
}

Additionally, you can provide an alignment parameter that controls the view position on a horizontal and vertical axis.

ZStack(
    alignment: Alignment(horizontal: .leading, vertical: .top)
) {
    Circle()
        .foregroundColor(.green)
    Text("Swift Wombat")
 }

There is one view modifier that you need to know to complete this task. It’s called a layoutPriority, and by providing a Double value for it, you can control the importance of views when the stack will layout them. By default, each has a priority set to zero. Set it to a higher value, and it will be authoritative when the stack calculates its frame.

You may wonder why this is important. To make a chat bubble, you have to use Image view as a background with resizable modifier.

Learn more about resizable and how to control Image resize zones with it

Resizable means that it will try to take all available space, but you want the background to fit the given text. It is a point where you should use layoutPriority.

ZStack {
    Image("bubble")
        .resizable(capInsets: EdgeInsets(top: 39, leading: 49, bottom: 39, trailing: 49))
        .renderingMode(.template)
        .foregroundColor(.blue)
     Text("Did you hear about the Swift Wombat website?")
        .foregroundColor(.white)
        .padding(.horizontal, 16)
        .padding(.vertical)
        .layoutPriority(1)
}

That way, the Text view will have a layout priority, and the Image will adjust to its size.

In the Xcode Playground example linked at the bottom of this article, you will find full code that will adjust the color and orientation of bubbles to distinguish messages that we send and receive.

Chat bubble Xcode Playground

31.1.2021 11:58How to use ZStack to create a chat bubble
https://kamil.id/2021/01/31/how-...

How to apply text modifiers based on a view state

https://kamil.id/2021/01/28/how-...

How to apply text modifiers based on a view state

I saw the “How to optionally make a Text italic?” question on Reddit, and I thought it would be good material for a short article. From this tutorial, you will learn how to prepare a conditional italic modifier. Additionally, you will find out how to make more generic modifiers to control every Text’s properties.

So, let’s start with italic. Let’s assume that you have a @State property wrapper that looks like this one:

@State private var isItalic = true

You can use the if ... else ... statement to control which version of Text is displayed.

if isItalic {
    Text("Swift Wombat")
        .italic()
} else {
    Text("Swift Wombat")
}

This way, you have to repeat a code, which you should try to avoid. Some text modifiers (strikethrough for example) have an active parameter, but not italic. You can easily create that with this extension:

import SwiftUI

extension Text {
    func italic(_ active: Bool) -> Text {
        guard active else { return self }
        return italic()
    }
}

And this alone will be an answer to the question from the first paragraph. But what if you want to handle also bold or font modifiers? You can iterate at the solution above and add an additional modifier to the Text extension.

import SwiftUI

extension Text {
    func active(
        _ active: Bool,
        _ modifier: (Text) -> Text
    ) -> Text {
        guard active else { return self }
        return modifier(self)
    }
}

It is a bit harder to read. This function takes two arguments. One is the active state that controls if modifier is applied on not, and the second one is a function - which gets a Text struct and returns its modified version. You can use it like this:

Text("Swift Wombat")
    .active(isTitle, { $0.font(.title) })

For functions without arguments, you may prepare another extension:

import SwiftUI

extension Text {
    func active(
        _ active: Bool,
        _ modifier: (Text) -> () -> Text
    ) -> Text {
        guard active else { return self }
        return modifier(self)()
    }
}

With it, you will be able to apply italic or bold even quicker:

Text("Swift Wombat")
    .active(isItalic, Text.italic)
    .active(isBold, Text.bold)

Note the capital case of the struct name Text in that version.

Active text modifier example Xcode Playground

28.1.2021 18:14How to apply text modifiers based on a view state
https://kamil.id/2021/01/28/how-...

How to store a Date using AppStorage

https://kamil.id/2021/01/25/how-...

How to store a Date using AppStorage

By default, @AppStorage property wrapper supports Int, String, Double, Data, or URL values. You can store other classes/structs/enums if you make them conform to the RawRepresentable protocol. This tutorial will learn how to keep Dates in UserDefaults handled by @AppStorage.

Learn basics of @AppStorage property wrapper.

If you write something like this:

@AppStorage("savedDate") var date: Date = Date()

the Swift compiler gives you an error because the @AppStorage property wrapper doesn’t support Date type.

But it supports objects that conform to RawRepresentable protocol, where that raw value is a String or Int. A proposed implementation may look like this:

import Foundation

extension Date: RawRepresentable {
    private static let formatter = ISO8601DateFormatter()
    
    public var rawValue: String {
        Date.formatter.string(from: self)
    }
    
    public init?(rawValue: String) {
        self = Date.formatter.date(from: rawValue) ?? Date()
    }
}

This code uses ISO8601DateFormatter to format a date to String and map it back. That formatter is static because creating and removing DateFormatters is an expensive operation. If you add the code above to your project, you will be able to read and store dates in your SwiftUI app.

struct DateView: View {
    @AppStorage("savedDate") var date: Date = Date()
    
    var body: some View {
        VStack {
            Text(date, style: .date)
            Text(date, style: .time)
            Button("Save date") { date = Date() }
        }
    }
}
AppStorage Date example in Xcode Project

25.1.2021 09:29How to store a Date using AppStorage
https://kamil.id/2021/01/25/how-...

How to use EnvironmentObjects with SwiftUI Live Preview

https://kamil.id/2021/01/20/how-...

How to use EnvironmentObjects with SwiftUI Live Preview

SwiftUI views in larger applications may use some configuration passed to them as environment objects. It is a very beneficial way when the config is needed somewhere deep into a view tree. If your view uses the @EnvironmentObject property wrapper, you can still debug and tweak it using live preview functionality.

Take a look at this example. In the article about home screen Quick Actions in SwiftUI, I’ve used ObservableObject called QuickActionService. You can use it to pass selected Quick Action to the given view.

import Foundation

enum QuickAction: String {
    case newMessage, search, inbox
}

final class QuickActionService: ObservableObject {
    @Published var action: QuickAction? = nil
}

When you provide that object using environmentObject modifier, you can listen to changes in it in any place down the view tree.

private let quickActionService = QuickActionService()

var body: some Scene {
    WindowGroup {
        ContentView()
            .environmentObject(quickActionService)
    }
}

Live previews, though, are rendered without that context of upper views. So you have to use the same environmentObject modifier in your preview code.

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environmentObject(QuickActionService())
    }
}

With a slight modification of QuickActionService:

final class QuickActionService: ObservableObject {
    @Published var action: QuickAction?
    
    init(initialValue: QuickAction? = nil) {
        action = initialValue
    }
}

you will be able to test different versions of your screen based on different values for quick action parameters.

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
                .environmentObject(QuickActionService())
            ContentView()
                .environmentObject(QuickActionService(initialValue: .newMessage))
        }
    }
}

20.1.2021 16:17How to use EnvironmentObjects with SwiftUI Live Preview
https://kamil.id/2021/01/20/how-...
Subscribe

🔝

Datenschutzerklärung    Impressum