avatarDevTechie

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

4505

Abstract

hljs-type">NavigationStack</span> { <span class="hljs-type">VStack</span>(alignment: .leading) { <span class="hljs-type">Text</span>(<span class="hljs-string">"Book title:"</span>) <span class="hljs-operator">...</span>

            <span class="hljs-type">HStack</span> {
                <span class="hljs-type">PhotosPicker</span>(
                    selection: <span class="hljs-variable">$selectedCover</span>,
                    matching: .images,
                    photoLibrary: .shared()
                ) {
                    <span class="hljs-type">Label</span>(<span class="hljs-string">"Add Cover"</span>, systemImage: <span class="hljs-string">"book.closed"</span>)
                }
                .padding(.vertical)
                
                <span class="hljs-type">Spacer</span>()
                
                <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> selectedCoverData {

                } <span class="hljs-keyword">else</span> {
                    <span class="hljs-type">Image</span>(systemName: <span class="hljs-string">"photo"</span>)
                        .resizable()
                        .scaledToFit()
                        .frame(width: <span class="hljs-number">100</span>, height: <span class="hljs-number">100</span>)
                }
            }
            
            <span class="hljs-type">Text</span>(<span class="hljs-string">"Published:"</span>)

<span class="hljs-operator">...</span></pre></div><p id="f8bf">Build and run</p><figure id="0c58"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*xRfhneLoCc4tkwqqhZPoMw.gif"><figcaption></figcaption></figure><p id="a24f">Before we display the selected image, let’s get its binary data our and populate our selectedCoverData. This operation is async in nature so we will use task modifier to extract data out of the selected photo.</p><div id="5fab"><pre><span class="hljs-keyword">import</span> SwiftUI <span class="hljs-keyword">import</span> SwiftData <span class="hljs-keyword">import</span> PhotosUI

<span class="hljs-keyword">struct</span> <span class="hljs-title class_">AddNewBookView</span>: <span class="hljs-title class_">View</span> { <span class="hljs-operator">...</span> .navigationTitle(<span class="hljs-string">"Add New Book"</span>) .task(id: selectedCover) { <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> data <span class="hljs-operator">=</span> <span class="hljs-keyword">try?</span> <span class="hljs-keyword">await</span> selectedCover<span class="hljs-operator">?</span>.loadTransferable(type: <span class="hljs-type">Data</span>.<span class="hljs-keyword">self</span>) { selectedCoverData <span class="hljs-operator">=</span> data } } } } }</pre></div><blockquote id="569c"><p>Notice that we are using the loadTransferable function with the photoPickerItem. This function grants us the capability to convert the foundational data, and as photoPickerItem conforms to the Transferable protocol, it enables our model to engage in system sharing and data transfer process.</p></blockquote><p id="1d16">Now when we have data in our selectedCoverData, we will go back to the picker section and add the code to display selected image.</p><div id="0b62"><pre><span class="hljs-keyword">import</span> SwiftUI <span class="hljs-keyword">import</span> SwiftData <span class="hljs-keyword">import</span> PhotosUI

<span class="hljs-keyword">struct</span> <span class="hljs-title class_">AddNewBookView</span>: <span class="hljs-title class_">View</span> { <span class="hljs-operator">...</span>

<span class="hljs-keyword">var</span> body: <span class="hljs-keyword">some</span> <span class="hljs-type">View</span> {
    <span class="hljs-type">NavigationStack</span> {
        <span class="hljs-type">VStack</span>(alignment: .leading) {
            <span class="hljs-operator">...</span>
            
            <span class="hljs-type">HStack</span> {
                <span class="hljs-type">PhotosPicker</span>(
                    selection: <span class="hljs-variable">$selectedCover</span>,
                    matching: .images,
                    photoLibrary: .shared()
            

Options

) {
                    <span class="hljs-type">Label</span>(<span class="hljs-string">"Add Cover"</span>, systemImage: <span class="hljs-string">"book.closed"</span>)
                }
                .padding(.vertical)
                
                <span class="hljs-type">Spacer</span>()
                
                <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> selectedCoverData, 
                    <span class="hljs-keyword">let</span> image <span class="hljs-operator">=</span> <span class="hljs-type">UIImage</span>(
                    data: selectedCoverData) {
                    <span class="hljs-type">Image</span>(uiImage: image)
                        .resizable()
                        .scaledToFit()
                        .clipShape(.rect(cornerRadius: <span class="hljs-number">10</span>))
                        .frame(width: <span class="hljs-number">100</span>, height: <span class="hljs-number">100</span>)
                        
                } <span class="hljs-keyword">else</span> {
                    <span class="hljs-type">Image</span>(systemName: <span class="hljs-string">"photo"</span>)
                        .resizable()
                        .scaledToFit()
                        .frame(width: <span class="hljs-number">100</span>, height: <span class="hljs-number">100</span>)
                }
            }
            <span class="hljs-operator">...</span>
        .navigationTitle(<span class="hljs-string">"Add New Book"</span>)
        .task(id: selectedCover) {
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> data <span class="hljs-operator">=</span> <span class="hljs-keyword">try?</span> <span class="hljs-keyword">await</span> selectedCover<span class="hljs-operator">?</span>.loadTransferable(type: <span class="hljs-type">Data</span>.<span class="hljs-keyword">self</span>) {
                selectedCoverData <span class="hljs-operator">=</span> data
            }
        }
    }
}

}</pre></div><p id="25ee">Build and run</p><figure id="788b"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*UXJH0zs_Bll4ObwUtOoXtA.gif"><figcaption></figcaption></figure><p id="c3f3">Let’s update the save button action to include image for the book cover.</p><div id="58e5"><pre><span class="hljs-keyword">import</span> SwiftUI <span class="hljs-keyword">import</span> SwiftData <span class="hljs-keyword">import</span> PhotosUI

<span class="hljs-keyword">struct</span> <span class="hljs-title class_">AddNewBookView</span>: <span class="hljs-title class_">View</span> { <span class="hljs-operator">...</span>

<span class="hljs-keyword">var</span> body: <span class="hljs-keyword">some</span> <span class="hljs-type">View</span> {
    <span class="hljs-type">NavigationStack</span> {
        <span class="hljs-type">VStack</span>(alignment: .leading) {
            <span class="hljs-operator">...</span>
            
            <span class="hljs-type">HStack</span> {
                
                <span class="hljs-operator">...</span>
                
                <span class="hljs-type">Button</span>(<span class="hljs-string">"Save"</span>) {
                    <span class="hljs-operator">...</span>
                    book.genres <span class="hljs-operator">=</span> <span class="hljs-type">Array</span>(selectedGenres)
                    
                    <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> selectedCoverData {
                        book.cover <span class="hljs-operator">=</span> selectedCoverData
                    }
                    
                    selectedGenres.forEach { genre <span class="hljs-keyword">in</span>
                        genre.books.append(book)
                        context.insert(genre)
                    }</pre></div><p id="1de5">Build, run and add a new book.</p><figure id="d3b5"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*7yo-a5QRV2uW4r0zjQbMhg.gif"><figcaption></figcaption></figure><p id="87e3" type="7">With that we have reached the end of this article. Thank you once again for reading. If you liked this, don’t forget to 👏 and follow 😍. Also visit us at https://www.devtechie.com</p></article></body>

SwiftData by Example: iOS 17 & SwiftUI 5 — Part 8

SwiftData by Example: iOS 17 & SwiftUI 5 — Part 8

Change is inevitable so let’s see how SwiftData handles the change. Our app is looking good but it’s missing book covers so let’s add them.

We will start with new property in the book model. Since we are dealing with images, we will store them as Data.

import Foundation
import SwiftData

@Model
final class Book {
    var title: String
    var author: String
    var publishedYear: Int
    
    var cover: Data?

This property is marked optional because we are adding a new attribute to our existing app and previously saved data should not be affected with this change as long as we are either adding new property as optional or with a default value.

Images are large and its not a good idea to store them in the data store directly(Due to performance issues), we can opt to store only the reference of the image and actual data for the image can be located somewhere else on the disk. We don’t have to do any of that manually as well, we can leverage SwiftData’s Attribute macro and define the schema configuration to store image’s binary data externally.

Update the model to include Attribute macro

import Foundation
import SwiftData

@Model
final class Book {
    ...
    
    @Attribute(.externalStorage)
    var cover: Data?

With this change, we are ready to store book covers.

Let’s update AddNewBook view to include a photo picker so users can select book covers from their photo library.

Import PhotosUI module first.

import SwiftUI
import SwiftData
import PhotosUI

struct AddNewBookView: View {

Add two new State properties, one to bind with photo picker and another to store the data form of selected image.

import SwiftUI
import SwiftData
import PhotosUI

struct AddNewBookView: View {
    ...
    
    @State private var selectedCover: PhotosPickerItem?
    @State private var selectedCoverData: Data?

Let’s add the UI to select photos.

import SwiftUI
import SwiftData
import PhotosUI

struct AddNewBookView: View {
    ...
    
    var body: some View {
        NavigationStack {
            VStack(alignment: .leading) {
                Text("Book title:")
                ...
                
                HStack {
                    PhotosPicker(
                        selection: $selectedCover,
                        matching: .images,
                        photoLibrary: .shared()
                    ) {
                        Label("Add Cover", systemImage: "book.closed")
                    }
                    .padding(.vertical)
                    
                    Spacer()
                    
                    if let selectedCoverData {

                    } else {
                        Image(systemName: "photo")
                            .resizable()
                            .scaledToFit()
                            .frame(width: 100, height: 100)
                    }
                }
                
                Text("Published:")
...

Build and run

Before we display the selected image, let’s get its binary data our and populate our selectedCoverData. This operation is async in nature so we will use task modifier to extract data out of the selected photo.

import SwiftUI
import SwiftData
import PhotosUI

struct AddNewBookView: View {
    ...
            .navigationTitle("Add New Book")
            .task(id: selectedCover) {
                if let data = try? await selectedCover?.loadTransferable(type: Data.self) {
                    selectedCoverData = data
                }
            }
        }
    }
}

Notice that we are using the loadTransferable function with the photoPickerItem. This function grants us the capability to convert the foundational data, and as photoPickerItem conforms to the Transferable protocol, it enables our model to engage in system sharing and data transfer process.

Now when we have data in our selectedCoverData, we will go back to the picker section and add the code to display selected image.

import SwiftUI
import SwiftData
import PhotosUI

struct AddNewBookView: View {
    ...
    
    var body: some View {
        NavigationStack {
            VStack(alignment: .leading) {
                ...
                
                HStack {
                    PhotosPicker(
                        selection: $selectedCover,
                        matching: .images,
                        photoLibrary: .shared()
                    ) {
                        Label("Add Cover", systemImage: "book.closed")
                    }
                    .padding(.vertical)
                    
                    Spacer()
                    
                    if let selectedCoverData, 
                        let image = UIImage(
                        data: selectedCoverData) {
                        Image(uiImage: image)
                            .resizable()
                            .scaledToFit()
                            .clipShape(.rect(cornerRadius: 10))
                            .frame(width: 100, height: 100)
                            
                    } else {
                        Image(systemName: "photo")
                            .resizable()
                            .scaledToFit()
                            .frame(width: 100, height: 100)
                    }
                }
                ...
            .navigationTitle("Add New Book")
            .task(id: selectedCover) {
                if let data = try? await selectedCover?.loadTransferable(type: Data.self) {
                    selectedCoverData = data
                }
            }
        }
    }
}

Build and run

Let’s update the save button action to include image for the book cover.

import SwiftUI
import SwiftData
import PhotosUI

struct AddNewBookView: View {
    ...
    
    var body: some View {
        NavigationStack {
            VStack(alignment: .leading) {
                ...
                
                HStack {
                    
                    ...
                    
                    Button("Save") {
                        ...
                        book.genres = Array(selectedGenres)
                        
                        if let selectedCoverData {
                            book.cover = selectedCoverData
                        }
                        
                        selectedGenres.forEach { genre in
                            genre.books.append(book)
                            context.insert(genre)
                        }

Build, run and add a new book.

With that we have reached the end of this article. Thank you once again for reading. If you liked this, don’t forget to 👏 and follow 😍. Also visit us at https://www.devtechie.com

Swiftui
Swiftui 5
Ios 17
iOS
Swiftdata
Recommended from ReadMedium