Images And Assets

In this post, we see how to use the images with SwiftUI and how to create some beautiful effect.

In beginning, we have to add an image to the assets of the XCode project. Click on the assets:

Drag your image under the AppIcon (you can use the image in the header of this post https://nicoladefilippo.com/wp-content/uploads/2021/04/sanfrancisco-1-1536×1152.jpg).

Upload sanfrancisco image

To use the image in the code:

import SwiftUI

struct ContentView: View {
    var body: some View {
        Image("sanfrancisco")
    }
}

In this way we see the image in bad way:

To resize the image we have to add the resizable:

Image("sanfrancisco")
            .resizable()

To remove the white on the top we choose to ignore the safe area in this way:

struct ContentView: View {
    var body: some View {
        Image("sanfrancisco")
            .resizable()
            .aspectRatio(contentMode: .fill)
            .edgesIgnoringSafeArea(.all)
            
    }
}

Try this code and see what happen.

Aspect ratio

To default the aspect ratio is fill (fill the space but not preserve the aspect ratio), to preserve the aspect ratio:

struct ContentView: View {
    var body: some View {
        Image("sanfrancisco")
            .resizable()
            .aspectRatio(contentMode: .fit)
    }
}

It’s not very nice, we can add rounded corner and padding:

struct ContentView: View {
    var body: some View {
        Image("sanfrancisco")
            .resizable()
            .aspectRatio(contentMode: .fit)
            .cornerRadius(40)
            .padding()
    }
}

To have:

Note: add the padding after the corner radius otherwise you will have the rounded corner.

Effects

There is also another effect, for example, rotation, I show how to have a photo with a cut edges.

struct ContentView: View {
    var body: some View {
        Image("sanfrancisco")
            .resizable()
            .aspectRatio(contentMode: .fit)
            .cornerRadius(40)
            .padding()
            .rotationEffect(.init(degrees: 30))
            .cornerRadius(40)
            .padding()
    }
}

Shape

It’s possible also to give a shape and size to the image:

struct ContentView: View {
    var body: some View {
        Image("sanfrancisco")
            .resizable()
            .aspectRatio(contentMode: .fit)
            .clipShape(Circle())
            .frame(width: 200)
    }
}

Overlay

Now see how to overlay an object with another not using the ZStack. First, we push the image on the topo using a VStack and a spacer; change the size using a frame and change the opacity:

struct ContentView: View {
    var body: some View {
        VStack {
            Image("sanfrancisco")
            .resizable()
            .aspectRatio(contentMode: .fit)
            .frame(width: 300)
            .opacity(0.5)
            Spacer()
        }
            
    }
}

Now we want to add a title on the top of the photo:

The code:

struct ContentView: View {
    var body: some View {
        VStack {
            Image("sanfrancisco")
            .resizable()
            .aspectRatio(contentMode: .fit)
            .frame(width: 300)
            .opacity(0.5)
            .overlay(
                Text("San Francisco bay ")
                    .fontWeight(.heavy)
                    .font(.system(.headline, design: .rounded))
                    .foregroundColor(.yellow)
                    .padding()
                    .background(Color.gray)
                    .cornerRadius(20)
                    .opacity(0.8)
                    .padding(),
                    alignment: .top
                
                )
            Spacer()
        }
            
    }
}

We added an overlay with a Text with properties that we already know (from previous post https://nicoladefilippo.com/text-and-button-with-swuiftui/), note instead of the alignment, with this we say where to set the text. Alignment is a property of the overlay (note the comma after the last padding).

Note: English is not my native language, so I’m sorry for some errors. I appreciate it if your correct me.

Text And Button with SwuiftUI

In 2019 Apple introduced the SwuifUI, a declarative language to develop the UI on Apple devices, in fact with this language is more simple to port an application from iPhone on the other devices type (AppleTV. iWatch, ..) and vice versa. Now in the Xcode, the default way to develop an application is to use SwuiftUI, instead is an optional use of UIKit (but it is not obsolete, in some case we need the uikit yet). Creating a new project we see the dialog to choose the project name and the Combobox to choose the toolkit.

After creating the project we see on the left sidebar the list of the files, in the centre the code and the preview, instead of on the right the property of the selected object.

The main word that we always will see in the SwiftUI is “view”, it’s a protocol, to show an object we need to use its protocol (to default it permits to use of default methods to manage the property of the objects). Another interesting word is “some”. This word means: “opaque type”. I find this nice post that explains that https://medium.com/@PhiJay/whats-this-some-in-swiftui-34e2c126d4c4 I suggest you read it. If you don’t want to read the full post about it, the main phase is: “Adding this keyword turns the return type into an opaque type which means that you and the compiler both know that this function will always return only one particular concrete type — you’ll just never know which one!”

The default piece of the code for a new project is:

struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
            .padding()
    }
}

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

The result is the text “Hello world” printed in the centre of the screen.

The ContentView_Previews is used to show the view in the preview window. This window has also a play button, so you can interact with the graphical object (how in a simulator).

Text

Come back to the text “Hello World”. It’s centred because every object is centred to default in the centre of the view. The padding is not a property of the whole label, but it’s a property of the text in the Label, in fact, if we change in:

struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
            .padding(.trailing, 20)
    }
}

you will see space after the exclamation mark.

Note that in the SwuiftUI world, trailing means right and leading meanings left. So try to change .trailing in .leading and see the effect.

It’s possible to change the font properties, using the default system font, for example title

struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
            .padding()
            .font(.title)
    }
}

To exercise you can try to see the other default size.

It’s possible also to use custom properties for, size, colour and so on.

struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
            .padding()
            .font(.system(size: 10, weight: .bold))
            .foregroundColor(Color.white)
            .background(Color.blue)       
    }
}

To use a custom family font

struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
            .padding()
            .font(.custom("Courier", size: 10))    
    }
}

A nice property is roudend, for the design.

struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
            .fontWeight(.bold)
            .padding()
            .font(.system( .title, design: .rounded ))
    }
}

Note the order of the method, it’s not casual, the method is called in the order top to bottom, if you can try to invert, you can have errors or different output (try to move the padding at the end).

It’s possible to change the properties using the panel on the right (how in the picture) but I think is much helpful to see them directly in the code.

Buttons

Now see the Button, cancel the code about the Text. Click on the plus button as in the figure and drag the button in the code.

Now replace the Label and Action with our code

struct ContentView: View {
    var body: some View {

        Button(action: {}) {
            Text("Touch me")
        }
    }
}

For now, we replace the action label with empty code (no action) and the content with a Text object.

To modify the look of the button we have to change the content so in this case the Text object.

struct ContentView: View {
    var body: some View {

        Button(action: {}) {
            Text("Touch me")
                .padding(10)
                .background(Color.blue)
                .foregroundColor(.white)
                .cornerRadius(10)
        }
    }
}

To have this button:

Used the method cornerRadius to have rounded corner

We can also use the system image (please download the San Francisco symbols from https://developer.apple.com/sf-symbols/)

struct ContentView: View {
    var body: some View {
        Button(action: {
            
        }) {
            Image(systemName: "cart")
                .foregroundColor(.purple)
                .font(.title)
        }
    }
}

To have:

We applied the font properties to the system image.

For this week that’s all. In the next post, I’ll show how to have a nice effect on the button and how to interact with them.

Note: English is not my native language, so I’m sorry for some errors. I appreciate it if your correct me.

SwiftUI and Shake Gesture

There isn’t a way to catch the shake gesture only using SwiftUI, so it’s necessary use these frameworks: Uikit and Combine.

Combine is very helpful in this case because how is wrote in the Apple documentation: “Combine declares publishers to expose values that can change over time, and subscribers to receive those values from the publishers.”

In the example, the goal is catch the Shake gesture with the UiKit, with the Combine framework we publish this event and get the combine published info in the SwiftUI view that is a subscriber. Let’s start.

We must create a Swift file, ShakeGestureManager.swift

import Foundation
import SwiftUI
import Combine
let messagePublisher = PassthroughSubject<Void, Never>()
class ShakeViewController: UIViewController {
override func motionBegan(_ motion: UIEvent.EventSubtype,
                              with event: UIEvent?) {
        guard motion == .motionShake else { return }
        messagePublisher.send()
    }
}
struct ShakeViewRepresentable: UIViewControllerRepresentable {
    
    func makeUIViewController(context: Context) ->
        ShakeViewController {
            ShakeViewController()
    }
func updateUIViewController(_ uiViewController: ShakeViewController,
                                context: Context) {
        
    }
}



Where the PassthroughSubject is a “A subject that broadcasts elements to downstream subscribers.” (from the Apple documentation). We need it to communicate with the SwiftUI View.

The ShakeViewController is a simple UIViewController that catches the shake Gesture. Like in the case of the Map, it’s not possible to use the UIViewController in the SwiftUI so we must create a struct that implement the UIViewControllerRepresentable that we can use in the SwiftUI View.

Now see the SwiftUI view:

import SwiftUI
struct ContentView: View {
    @State var shaked = false
    
    var body: some View {
        NavigationView {
            VStack{
                ZStack {
                    ShakeViewRepresentable()
                                .allowsHitTesting(false)
                    VStack {
                        Text("Shake device to change view!")
                    }
                    
                }.onReceive(messagePublisher) { _ in
                    self.shaked = true
                }
                NavigationLink(destination: SecondView(), isActive: $shaked) {
                    EmptyView()
                }
            }
        }
    }
}



The state variable shaked it used to store the shaked event. In the navigationview there is a ZStack, where at the bottom (first element) there is the ShakeViewRepresentable that can’t get touch event from the user because allowsHitTesting is false, on the top there is a simple Text message.

When the ZStack receives the messagePublisher, the shaked variable become true and the NavigationLink become active so the view navigates to the SecondView (that you can create how you want). Note the use of the EmptyView from the NavigationLink, it used to show nothing but to have a working link.

That’s all. I hope that it can be helpful. You can get the code here.

QML vs SwiftUI (Part 3)

In this article we’ll see the different use of the lists that is a very most used components in the mobile apps. We starting with SwiftUI, code the example show how create a simple list and the right swipe action to delete a row.

struct ContentView: View {
    @State var names = ["Albert", "Tom", "Jeff", "Jennifer", "Steve", "Bob"]
    var body: some View {
        List {
            ForEach(names.indices, id: \.self) { index in
                Text(self.names[index]) // Delegate
            }
            .onDelete { (indexSet) in
                self.names.remove(atOffsets: indexSet)
            }
        }
    }
}



The list is created with the keyword List, with For each for every elements of the array names is created an object Text that show the single name. If the model is more complex that a simple list of name and we have to show also an image we can add in the For each block the Image object.

The instruction “id: .self” is necessary to identify every single row in unique way, in this case this identifier is the index of the array. Other important keyword is “@State” with this we say to SwiftUi to monitoring the variable names and update the ui when it changes.

In the on Delete function we do the action the we want doing the right swipe, in this case delete the element. Before to switch to QML, we see an example a bit more complex:

struct People: Identifiable {
    var id = UUID()
    var name: String
    var image: String
}struct ContentView: View {
    @State var peoples = [People(name:"Albert", image: "albert.png"), People(name: "Tom", image: "tom.png"),People(name: "Jeff", image:"jeff.png"),People(name: "Jennifer",image: "jennifer.png"), People(name: "Steve", image: "steve.png"), People(name: "Bob", image: "bob.png")]
    var body: some View {
        List {
            ForEach(peoples, id: \.id) { people in
                HStack {
                    Text(people.name)
                    Image(people.image)
                }
            }
            .onDelete { (indexSet) in
                self.peoples.remove(atOffsets: indexSet)
            }
        }
    }
}



In this case we have the struct People that implements the protocol Identifiable, so we have am unique identifier for any instance of the struct. In the ForEach we now use it do identify the row also added a horizonthal stack to create a row with text and image. Now see that same example in QML:

ListView {
    id: listView
    anchors.fill: parent
    model: ListModel {
        ListElement { img: "bob.png"; name: "Bob" }
        ListElement { img: "jennifer.png"; name: "Jennifer" }
        ListElement { img: "tom.png"; name: "Tom" }
        ListElement { img: "denise.png"; name: "Denise" }
    }
    delegate: SwipeDelegate {
        id: swipeDelegate
        text: model.name
        width: parent.width
        
        ListView.onRemove: SequentialAnimation {
            PropertyAction {
                target: swipeDelegate
                property: "ListView.delayRemove"
                value: true
            }
            NumberAnimation {
                target: swipeDelegate
                property: "height"
                to: 0
                easing.type: Easing.InOutQuad
            }
            PropertyAction {
                target: swipeDelegate
                property: "ListView.delayRemove"
                value: false
            }
        }
        
        contentItem: Item {
            RowLayout {
                Text {
                    text: swipeDelegate.text
                    font: swipeDelegate.font
                    elide: Text.ElideRight
                    visible: swipeDelegate.text
                    horizontalAlignment: Text.AlignLeft
                    verticalAlignment: Text.AlignVCenter
                }
                Image {
                    source: model.img
                    visible: swipeDelegate.text
                    horizontalAlignment: Text.AlignLeft
                    verticalAlignment: Text.AlignVCenter
                }
            }
        }
        
        swipe.right: Label {
            id: deleteLabel
            text: qsTr("Delete")
            color: "white"
            verticalAlignment: Label.AlignVCenter
            padding: 12
            height: parent.height
            anchors.right: parent.right
            
            SwipeDelegate.onClicked: listView.model.remove(index)
            
            background: Rectangle {
                color: deleteLabel.SwipeDelegate.pressed ? Qt.darker("tomato", 1.1) : "tomato"
            }
        }
    }
}



In this case the QML is a bit more complex because we have to customize the label to have the same look & feel of iOS but in QML we don’t need to use any “@State” keyword to force the gui update.

At the end I think that it’s simple for QML developer to use SwiftUI, a lot of things are similar and both the languages ​​are declaratives. Instead I think that for iOS developer is not simple to use both, is necessary more experience in the declarative world.

QML vs Swift (part 2)

In this part we’ll see how use the Button in both the languages.

In QML we define the button in this way:

Button {  text: "A button"  id: control  onClicked: {
   // Your action  }}



Where in the “text” property we set the label of the button and in the onClicked is executed what we need when the button is tapped/clicked. Instead in SwiftUI we have:

Button(action: {       // Your action
      }){
        Text("button")
             
      }



For the SwiftUI button the code to execute when the user clicks the button go in the action block. Instead for the property about the text can be changed modifying the properties of the object Text. A example to customization the button with QML:

Button {
    text: "A button"
    id: control

    onClicked: {
        
    }
    contentItem: Text {
            text: control.text
            font: control.font
            opacity: enabled ? 1.0 : 0.3
            color: control.down ? "#17a81a" : "#21be2b"
            horizontalAlignment: Text.AlignHCenter
            verticalAlignment: Text.AlignVCenter
            elide: Text.ElideRight
    }
    
    background: Rectangle {
        implicitWidth: 100
        implicitHeight: 25
        border.width: control.activeFocus ? 2 : 1
        border.color: "#888"
        radius: 40
        gradient: Gradient {

            orientation: Gradient.Horizontal
            GradientStop { position: 0 ; color: control.pressed ? "red" : "red" }
            GradientStop { position: 1 ; color: control.pressed ? "blue" : "blue" }
        }
    }
}



To change the properties of the text we must assign an Text Object to the contenrItem (in this case QML and SwiftUI are similar) and to change the bacground we can assign a rectangle with solid color or with gradient. It’s possible also use other object (i.e. image) for the background. A example to customization the button with SwiftUI:

Button(action: {}) {
            Text("Button")
                .fontWeight(.bold)
                .font(.title)
                .padding()
                //.background(Color.red)
                .background(LinearGradient(gradient: Gradient(colors: [Color.red, Color.blue]), startPoint: .leading, endPoint: .trailing))
                .cornerRadius(40)
                .foregroundColor(.white)
                .padding(10)
        }



In SwiftUI we change the background color (solid or not) modifying only the properties of the Text object because the Text object is used to “show” the button. If we want use an image for the button we must replace the Text object with the Image object.