Joshua Walsh - Big Nerd Ranch Tue, 29 Nov 2022 12:46:54 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.5 Be a square – create custom shapes with SwiftUI https://bignerdranch.com/blog/be-a-square-create-custom-shapes-with-swiftui/ https://bignerdranch.com/blog/be-a-square-create-custom-shapes-with-swiftui/#respond Wed, 28 Apr 2021 14:32:27 +0000 https://bignerdranch.com/blog/be-a-square-create-custom-shapes-with-swiftui/ Shapes out of the box This Swift UI tutorial will introduce you to the techniques and code needed to add custom shapes in Swift UI’s framework. We can create our own shape by drawing the Path ourselves, and these custom shapes are used to display a task that is running or to show feedback to […]

The post Be a square – create custom shapes with SwiftUI appeared first on Big Nerd Ranch.

]]>
Shapes out of the box

This Swift UI tutorial will introduce you to the techniques and code needed to add custom shapes in Swift UI’s framework. We can create our own shape by drawing the Path ourselves, and these custom shapes are used to display a task that is running or to show feedback to the user when interacting with an element on the screen.

SwiftUI gives us some powerful tools out of the box, shapes being one of them. Apple provides us shapes like CapsuleCircleEllipseRectangle, and RoundedRectangle. A shape is a protocol that conforms to the Animatable, and View protocols, which means we can configure their appearance and behavior. But we can also create our own shape with the power of the Path struct! A Path is simply an outline of a 2D shape that we will draw ourselves. If you’re thinking, ok but how is this practical? Custom shapes and animations are used to display a task that is running or to show feedback to the user when interacting with an element on the screen. Here’s where we’re going, and we’ll get there by building the vehicle body, adding some animation and styling, then adding the sunset behind it. Let’s get started!

Plotting out points

Since we’re working on an iOS application, the origin of CGRect will be in the upper-left, and the rectangle will extend towards the lower-right corner. To build our shape we’re going to start the origin in the bottom-left corner and work clockwise. You can read the official Apple Documentation for more details on CGRect.

Based on this we can plan our shapes before fumbling around with numbers and CGPoint values. For this example, we’ll build a vehicle and animate it to look like it’s moving. I’ve drawn out the frame of the vehicle using Path, and we’ll use the Circle shape to make the wheels and hubcaps. Again, here is what it will look like:

A simple car shape of body and two wheels viewed from the side. It's body is a curved wedge tapering to the right. Each wheel is made of an outer tire circle and an inner hubcap circle.Create a struct that conforms to the Shape protocol. In it we need to add the func path(in rect: CGRect) -> Path method. This is what allows us to draw our shape.

struct VehicleBody: Shape {
    // 1.
    func path(in rect: CGRect) -> Path {
        // 2.
        var path = Path()
        // 3.
        let bottomLeftCorner = CGPoint(x: rect.minX, y: rect.maxY)
        path.move(to: bottomLeftCorner)
        // 4.
        path.addCurve(to: CGPoint(x: rect.maxX, y: rect.maxY * 0.7),
                      control1: CGPoint(x: rect.maxX * 0.1, y: rect.maxY * 0.1),
                      control2: CGPoint(x: rect.maxX * 0.1, y: rect.maxY * 0.4))

        path.addCurve(to: CGPoint(x: rect.maxX * 0.8, y: rect.maxY),
                      control1: CGPoint(x: rect.maxX * 0.9, y: rect.maxY),
                      control2: CGPoint(x: rect.maxX, y: rect.maxY))

        // 5.
        path.closeSubpath()
        // 6.
        return path
    }
}

Code breakdown

Let’s go through what’s happening in the code.

  1. Within our struct, we need to define the function path(in:), which is required by the Shape protocol. This returns a Path which we will create. It takes a CGRect parameter that will help us lay out our shape.
  2. Add a local variable called path that is a Path. Remember a Path is the outline of a 2D shape.
  3. Tell the path where our starting point will be using the move(to: CGPoint) function. Here is where our parameter CGRect will help us find our starting point. Thinking in terms of a grid or coordinates, we want our shape to start at the bottom-left corner. A CGRect is a structure that contains the location and dimensions of a rectangle, and a CGPoint is a structure that contains a point in a two-dimensional coordinate system. For iOS the bottom-left corner of a CGRect is the minX or 0, and maxY or the largest value of y on the coordinate system.
  4. Let’s add two curves that will serve as the back, and front our vehicle. path has a function called addCurve, and it does exactly what the name says. It adds a cubic Bézier curve to the path with specified end and control points. The endPoint is the endpoint of the curve. Essentially where you want the curve to end. The path the curve will take starts at our move(to:) point, rect.minX, and rect.maxYcontrolPoint1 and controlPoint2 determine the curvature of the segment. The addCurve must be called after the move(to:) or after a previously created line. If the path is empty, this method does nothing. This method can seem overwhelming at first, so I’d suggest reading Apple's official documentation. If you’re wondering how I ended up with these control points, I simply changed each point until I was happy with the shape. Feel free to modify these points in your own shape. This is what the curves should look like:
  1. A basic outline of a car.We can then close off our shape’s path by calling closeSubpath(). This will create a straight-line segment from the last to the first point of our shape.
  2. Finally, return our completed path.

The hard part is over

Now that we have our frame, let’s add some wheels using a shape we get for free. If you haven’t guessed it already, we’re going to use the Circle shape for our wheels. In order to line things up correctly, we need to layout our view with a few ZStacks. Let’s create a new struct that we’ll build our vehicle parts in.

struct Vehicle: View {
    var body: some View {
        // 1.
        ZStack {
            // 2.
            VStack(spacing: -15) {
                // 3.
                VehicleBody()
                // 4.
                HStack(spacing: 30) {
                    // Back wheel
                    ZStack {
                        Circle()
                            .frame(width: 30, height: 30)
                        Circle()
                            .fill(Color.gray)
                            .frame(width: 20, height: 20)
                    }
                    // Front wheel
                    ZStack {
                        Circle()
                            .frame(width: 30, height: 30)
                        Circle()
                            .fill(Color.gray)
                            .frame(width: 20, height: 20)
                    }
                }            
            }
            // 5.
            .frame(width: 150, height: 100)
        }
    }
}

Code breakdown

  1. We want our shapes to overlap some so our wheels aren’t floating beneath the vehicle frame. Using a ZStack allows us to overlap views.
  2. Now a ZStack isn’t enough to put our parts in the correct placement. Adding a VStack will stack our frame and wheels, vertically. We can then adjust the spacing to line our wheels up so half their height aligns with the bottom of the frame.
  3. Add the VehicleBody()
  4. Let’s create our wheels. Our wheels will have the tire and hubcap appearance. First, we know that they will be horizontally aligned, so wrap them in a HStack and give them a spacing of 30. Next, our wheels will each be wrapped in a ZStack so we can place the hubcap on top of the wheel. First add the wheel shape with Circle() and give it a frame with a width and height of 30. Then, add the hubcap with a width and height of 20. Give the hubcap a fill color of gray so we can see it over the wheel. Repeat this for the second wheel.
  5. Set a fixed-size frame for the Vehicle view.

Lights, camera, animation!

Now that we have the frame and wheels of our vehicle we’re going to add some animations and ride off into the sunset.

Let’s animate!

Since we’ve just built a sweet vehicle that looks like it can handle some off-roading, I think our suspension should animate to show that. We don’t need a lot of code to make this happen, but we need to take care to animate the right elements. For this our animation will be on the parent VStack of the VehicleBody. We need to add a @State property to tell our view to animate, and two modifiers after the frame modifier of the VStack placing the wheels relative to the body:

struct Vehicle: View {
    // 1.
    @State var isPlayingAnimation: Bool = false

    var body: some View {
        ZStack {            
            VStack(spacing: -15) {
                ...VehicleBody()
                ...HStack(spacing: 30)
            }
            // 2.
            .offset(y: isPlayingAnimation ? -3 : 0)
            // 3.
            .animation(Animation.linear(duration: 0.5).repeatForever(autoreverses: true))
        }
    }
}

Code breakdown

  1. Add a @State property to manage our animation offset y position just above var body: some View.
  2. Add the offset modifier to change the y position of our ZStack. Place this just after the .frame modifier. We want the vehicle to move up and down like a bouncing effect.
  3. Call the animation modifier with a linear type. Finally, add the .repeatForever(autoreverses: true) function so our vehicle will appear to bounce…forever.

We’re going to add the same functions to the HStack that contains our Circle shapes, but we’ll change the y position and animation duration slightly. This will give us a nice suspension effect.

.offset(y: isPlayingAnimation ? -2 : 0)
.animation(Animation.linear(duration: 0.4).repeatForever(autoreverses: true))

Ah, the sunset

We’ll add one more shape to create our sunset, and then we’ll style our vehicle a bit. Our sunset will be in the shape of a Circle. Let’s add it directly inside our top ZStack.

Circle()
    .fill(LinearGradient(gradient: Gradient(colors: [.yellow, .red, .clear, .clear]), startPoint: .top, endPoint: .bottom))
    .frame(width: 130, height: 130)
    .shadow(color: .orange, radius: 30, x: 0, y: 0)

I’ve added some style to my vehicle, but feel free to style yours however you’d like. Here’s mine:

.fill(LinearGradient(gradient: Gradient(colors: [.purple, .red, .orange]), startPoint: .topTrailing, endPoint: .bottomLeading))

Lastly, in order to see our animation work, we need to add the onTapGesture function to our top ZStack and inside the closure toggle the isPlayingAnimation bool. Now we can interact with our animation simply by tapping it.

.onTapGesture {
    self.isPlayingAnimation.toggle()
}

You can see the animation right inside the canvas preview of Xcode by pressing the play button above the preview device. Or build and run on a simulator  .

Conclusion

Our example shows just how easy it is to create a custom shape in SwiftUI. We barely scratched the surface of what we can do here, so I encourage you to explore some of the other functions in Path such as addArc or addQuadCurve. For example, try using quad curves to build a vehicle with more rounded corners.

The post Be a square – create custom shapes with SwiftUI appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/be-a-square-create-custom-shapes-with-swiftui/feed/ 0
An intro to Machine Learning in iOS with Swift, and Playgrounds https://bignerdranch.com/blog/an-intro-to-machine-learning-in-ios-with-swift-and-playgrounds/ Mon, 03 Aug 2020 23:31:59 +0000 https://www.bignerdranch.com/?p=4457 So you heard about machine learning and Apples framework CoreML and wanted to give it a whirl. If your initial thought was that it's too complicated and didn't know where to begin, don't worry, it's not, and I'll walk you through it.

The post An intro to Machine Learning in iOS with Swift, and Playgrounds appeared first on Big Nerd Ranch.

]]>
So, you’ve heard about machine learning and Apple’s framework CoreML and want to give it a whirl. If your initial thought is that it’s too complicated and that didn’t know where to begin, don’t worry—it’s not, and I’m here to walk you through it.

But wait, why do we even need machine learning and how can it help us? Machine learning allows you to take large data sets and apply complex mathematical calculations over and over, faster and faster.

Apple has made the entry-level for machine learning quite accessible and wrapped it up in an all-in-one package. Swift, Xcode, and Playgrounds are all the tools you’re going to need to train a model, then implement it into a project. If you haven’t already, you’ll want to download Xcode. Now, let’s jump right in.

A note before we begin. This tutorial will be done in Playgrounds to help understand the code behind training models, however, CreateML is a great alternative to training models with no machine learning experience. It allows you to view model creation workflows in real-time.

Training a Model

One of the first things you’re going to need before opening Xcode is a dataset. What’s a dataset you say? It’s simple, a dataset is a collection of data, such as movie reviews, locations of dog parks, or images of flowers. For your purposes, we’re going to be using a file named appStore_description.csv to train your model. There are a handful of resources to find datasets, but we’re using kaggle.com, which is a list of AppStore app descriptions and the app names. We’ll use this text to help predict an app for the user based on their text input. Our model will be a TextClassifier, which learns to associate labels with features of our input text. This could come in the form of a sentence, paragraph, or a whole document.

  • Download the dataset here.
  • Your dataset may have a column named track_name, you can open the csv file and rename that column to app_name so it’s consistent with this example.

Now that you have your dataset you can open Xcode 🙌.

  1. First, create a new Playground using the macOS template and choose BlankWe use macOS because the CreateML framework is not available on iOS.
  2. Delete all the code in the Playground and import Foundation and CreateML.
  3. Add the dataset to the Playground’s Resources folder.

Here is what your Playground will look like and what is going on inside it:

import Foundation
import CreateML

//: Create a URL path to your dataset and load it into a new MLDataTable
let filePath = Bundle.main.url(forResource: "appStore_description", withExtension: "csv")
let data = try MLDataTable(contentsOf: filePath)
//: Create two mutually exclusive, randomly divided subsets of the data table
//: The trainingData will hold the larger portion of rows
let (trainingData, testData) = data.randomSplit(by: 0.8)
//: Create your TextClassifier model using the trainingData
//: This is where the `training` happens and will take a few minutes
let model = try MLTextClassifier(trainingData: trainingData, textColumn: "app_desc", labelColumn: "app_name")
//: Test the performance of the model before saving it. See an example of the error report below
let metrics = model.evaluation(on: testData, textColumn: "app_desc", labelColumn: "app_name")
print(metrics.classificationError)

let modelPath = URL(fileURLWithPath: "/Users/joshuawalsh/Desktop/AppReviewClassifier.mlmodel")
try model.write(to: modelPath)

Once you have this in your Playground, manually run it to execute training and saving the model. This may take a few minutes. Something to note is that our original csv dataset file is 11.5 MB and our training and test models are both 1.3 MB. While these are relatively small datasets, you can see that training our model drastically reduces the file size 👍.

Example Error Report

Printing the metrics is optional when creating your model, but it’s good practice to do this before saving. You’ll get an output something like this:

Columns:
    actual_count    integer
    class    string
    missed_predicting_this    integer
    precision    float
    predicted_correctly    integer
    predicted_this_incorrectly    integer
    recall    float
Rows: 1569
Data:
+----------------+----------------+------------------------+----------------+---------------------+
| actual_count   | class          | missed_predicting_this | precision      | predicted_correctly |
+----------------+----------------+------------------------+----------------+---------------------+
| 1              | "HOOK"         | 1                      | nan            | 0                   |
| 1              | ( OFFTIME ) ...| 0                      | nan            | 1                   |
| 1              | *Solitaire*    | 0                      | nan            | 1                   |
| 1              | 1+2=3          | 0                      | nan            | 1                   |
| 1              | 10 Pin Shuff...| 0                      | nan            | 1                   |
| 1              | 10 – �..       | 0                      | nan            | 1                   |
| 1              | 100 Balls      | 0                      | nan            | 1                   |
| 1              | 1010!          | 0                      | nan            | 1                   |
| 1              | 12 Minute At...| 0                      | nan            | 1                   |
| 1              | 20 Minutes.f...| 0                      | nan            | 1                   |
+----------------+----------------+------------------------+----------------+---------------------+
+----------------------------+----------------+
| predicted_this_incorrectly | recall         |
+----------------------------+----------------+
| 0                          | 0              |
| 0                          | 0              |
| 0                          | 0              |
| 0                          | 0              |
| 0                          | 0              |
| 0                          | 0              |
| 0                          | 0              |
| 0                          | 0              |
| 0                          | 0              |
| 0                          | 0              |
+----------------------------+----------------+
[1569 rows x 7 columns]

Adding the model to your app

Now that you have your model trained and saved somewhere on your computer, you can create a new iOS project.

  1. Let’s make it a single view app and we’ll call it AppPredictor, and use Storyboards.
  2. Find where you saved your model, and drag that file into your project in Xcode.
  3. In ViewController.swift import UIKitNaturalLanguage and CoreML.
import UIKit
import NaturalLanguage
import CoreML

Using your custom text classifier

For simplicity’s sake, your UI will have 3 elements. A text field, a label, and a button. We’re going for functionality here, but feel free to update your designs however you see fit. Next, add the text field, label, and button to your view controller in the storyboard. The text field and label will be IBOutlets, and your button will be an IBAction.

@IBOutlet weak var textField: UITextField!
@IBOutlet weak var appNameLabel: UILabel!

@IBAction func predictApp(_ sender: Any) {

}

Now add a reference to your classifier like so:

private lazy var reviewClassifier: NLModel? = {
    // Create a custom model trained to classify or tag natural language text.
    // NL stands for Natual Language
    let model = try? NLModel(mlModel: AppReviewClassifier().model)
    return model
}()

Let’s then create a function that takes in a string, and returns a string based on the user’s input.

private func predict(_ text: String) -> String? {
    reviewClassifier?.predictedLabel(for: text)
}

Back in predictApp, add the predict function and pass the text fields text in the argument.

appNameLabel.text = predict(textField.text ?? "")

Build and run your app and let’s see what you get. Describe an app that you can’t quite remember the name of, but you know what it does. I described two similar types of apps but got different results 👍.

Conclusion

Machine learning isn’t all that scary or complicated once you break it down into digestible chunks. Finding the right dataset can often be the biggest hurdle. Now that you have the basics down you should explore some of the other classifier types, and train different models.

The post An intro to Machine Learning in iOS with Swift, and Playgrounds appeared first on Big Nerd Ranch.

]]>