SwiftUI Design Concepts

Views:

VStack : arrange views vertically

HStack : arrange views horizontally

ZStack : arrange views one on another

Color.purple : will create view of purple color

SF Symbols :

// Use "systemName" when you want to use "SF Symbols"
 Image(systemName: "hand.thumbsup.fill") // but it will show blue
 image to show original image we have to write as

Image(systemName: "hand.thumbsup.fill")
      .renderingMode(.original)

Divider 

Divider() 

Spacer 
Spacers push things away either vertically or horizontally

Spacer() // take system default spacing 
Spacer(minLength: 10)  //take min

Form

Form {
 Text("This is a Form!")
}

Circle 

to create circle

Circle()

RoundedRectangle

to create RoundedRectangle

RoundedRectangle(cornerRadius: 8)
 .foregroundColor(.purple)

Capsule

to create capsule like UI

Capsule()
 .foregroundColor(.purple)

NavigationView

Section with header and footer

Screenshot 2020-01-22 at 3.32.22 PM.png

List row inset

Screenshot 2020-01-22 at 3.08.17 PM.png

Navigating from first screen to second and back

//  ContentView.swift

import SwiftUI

struct ContentView: View {
    
    var body: some View {
            Navigation_BackButtonHidden()
    }
}

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

// First Screen
struct Navigation_BackButtonHidden: View {
    var body: some View {
        NavigationView {
            ZStack {
                Color("Theme3BackgroundColor")
                VStack(spacing: 2) {
                    NavigationLink("Go To Detail",
                                   destination: BackButtonHiddenDetail())
                    Spacer()
                }
                .font(.title)
                .padding(.top, 70)
            }
            .navigationBarTitle(Text("Navigation Views"))
            .edgesIgnoringSafeArea(.bottom)
        }
    }
}
// Second Screen
struct BackButtonHiddenDetail: View {
    @Environment(\.presentationMode) var presentationMode
    
    var body: some View {
        ZStack {
            Color("Theme3BackgroundColor")
            VStack(spacing: 25.0) {
            
                Text("This nav bar has no back button because it was hidden on this view."
                Button("Go Back") {
                    self.presentationMode.wrappedValue.dismiss()
                }
                Spacer()
            }
            .font(.title)
            .padding(.top, 50)
        }
        .navigationBarTitle(Text("Detail View"), displayMode: .inline)
        .edgesIgnoringSafeArea(.bottom)
            // Hide the back button
            .navigationBarBackButtonHidden(true)
    }
}

Geometry Reader

GeometryReader is a container view that pushes out to fill up all available space. You use it to help with positioning items within it.

GeometryReader {_ in
 Text("Views center automatically inside the GeometryReader")
 .font(.title)
 } 

Use the geometry reader’s variable to help position child views at different locations within the geometry’s view instead of it being in the center.

GeometryReader { geometry in
 Text("Upper Left")
 .font(.title)
 .position(x: geometry.size.width/5,
 y: geometry.size.height/10)

 Text("Lower Right")
 .font(.title)
 .position(x: geometry.size.width - 90,
 y: geometry.size.height - 40)
 } 

Use the geometry reader when you need to get the height and/or width of a space.

GeometryReader { geometry in
 VStack(spacing: 10) {
 Text("Width: \(geometry.size.width)") .  // will get some width 
 Text("Height: \(geometry.size.height)") . // will get some height
 }
 .foregroundColor(.white)
 } 

to get local co-ordinates

GeometryReader { geometry in
 VStack(spacing: 10) {
 Text("X: \(geometry.frame(in: CoordinateSpace.local).origin.x)")
 Text("Y: \(geometry.frame(in: CoordinateSpace.local).origin.y)")
 }
 .foregroundColor(.white)
 }
 .background(Color.pink) 

to get Global co-ordinates

GeometryReader { geometry in
 VStack(spacing: 10) {
 Text("X: \(geometry.frame(in: .global).origin.x)")
 Text("Y: \(geometry.frame(in: .global).origin.y)")
 }
 .foregroundColor(.white)
 }
 .background(Color.pink)

to get min mid mix co-ordinates 

Screenshot 2020-01-22 at 11.03.53 AM.png

to read safe area insets it has.

Text("geometry.safeAreaInsets.leading: \(geometry.safeAreaInsets.leading)")

Control Views

1. Button

Button("Default Button Style") {
 // Your code here
 } 

Button(action: {// Your code here}) {
 Text("Headline Font")
 .font(.headline)
 } 

You can add more than one text view to a button. By default they are composed within a VStack.

Button(action: {}, label: {
 Text("New User")
 .font(.title)
 Text("(Register Here)")
}) 
Screenshot 2020-01-22 at 11.30.15 AM.png

using HStack

Button(action: {}, label: {
 HStack {
 Text("Forgot Password?")
 Text("Tap to Recover")
 .foregroundColor(.orange)
 }.font(.title)
})
Screenshot 2020-01-22 at 11.31.42 AM.png

2. Datepicker

@State private var dateInForm = Date() 
DatePicker("DatePicker Collapsed (Default)",
 selection: $dateInForm,
 displayedComponents: .date) 

From a Specific Date or Time

@State private var arrivalDate = Date()
 let fromToday = Calendar.current.date(byAdding: .minute,
 value: -1, to: Date())! 
DatePicker("", selection: $arrivalDate, in: fromToday...,
 displayedComponents: .date) 

To a Specific Date or Time

@State private var arrivalDate = Date()
 let mainColor = Color("LightPinkColor")
DatePicker("", selection: $arrivalDate, in: ...Date(),
 displayedComponents: [.date, .hourAndMinute]) 

With Minimum and Maximum Date Range

var withinNext30Days: ClosedRange {
 let today = Calendar.current.date(byAdding: .minute, value: -1,
 to: Date())!
 let next30Days = Calendar.current.date(byAdding: .day, value: 30,
 to: Date())!
 return today...next30Days
} 
DatePicker("", selection: $nextFullMoonDate,
 in: withinNext30Days,
 displayedComponents: .date) 

3. Picker

@State private var yourName = "Mark"
Picker(selection: $yourName, label: Text("Your name")) {
 Text("Paul").tag("Paul")
 Text("Chris").tag("Chris")
 Text("Mark").tag("Mark")
 Text("Scott").tag("Scott")
 Text("Meng").tag("Meng)
}

4. Segmented Control

@State private var yourName = "Mark"
Picker(selection: $yourName, label: Text("Your name")) {
 Text("Paul").tag("Paul")
 Text("Chris").tag("Chris")
 Text("Mark").tag("Mark")
}
.pickerStyle(SegmentedPickerStyle())

5. Toggle

@State private var isOn = true
Toggle(isOn: $isOn) { Text("Toggle") } 

6. Stepper

@State private var stepperValue = 1
Stepper(value: $stepperValue) {
 Text("Bound Stepper: \(stepperValue)")
 }

@State private var values = [0, 1] 
Stepper(onIncrement: {self.values.append(self.values.count)},
 onDecrement: {self.values.removeLast()}){ 
Text("Stepper") 
}

//You can set a range for the stepper too. 
Stepper(value: $stars, in: 1...5) {
 Text("Rating")
 }

7. Slider

@State private var age = 18.0
Slider(value: $age, in: 1...100, step: 1) 

8. TextField

TextFields, need to bind to a local variable.

@State private var userName = ""
TextField("", text: $userName)
 .textFieldStyle(RoundedBorderTextFieldStyle())  // to change textfield font style
 .multilineTextAlignment(.leading) // Default

Keyboard Type
TextField("Enter Phone Number", text: $textFieldData)
 .keyboardType(UIKeyboardType.phonePad) // Show keyboard for phone numbers

Disabling Autocorrect
TextField("Enter Last Name", text: $textFieldData)
 .disableAutocorrection(true) // Don't offer suggestions

9. SecureField

TextFields, need to bind to a local variable.

@State private var password = ""
SecureField("password", text: $password) 

10. TabView

Screenshot 2020-01-24 at 9.39.46 AM.png
Screenshot 2020-01-24 at 9.40.17 AM.png

11. List
List is use to create vertical scrollable content

//static list
List {
 Text("Line One")
 Text("Line Two"))
 Image("profile")
 Button("Click Here", action: {})
 .foregroundColor(.orange)
 HStack {
 Spacer()
Text("Centered Text")
 Spacer()
 }.padding()
 } 

// List with Data
List(stringArray, id: \.self) { string in
 Text(string)
 } 

Delete Rows

List {
 Section(header: Text("To Do").padding()) {
 ForEach(data, id: \.self) { datum in
 Text(datum).font(Font.system(size: 24)).padding()
 }
 .onDelete(perform: delete) // Enables swipe to delete
 }
 }

func delete(at indexes: IndexSet) {
 if let first = indexes.first {
 data.remove(at: first)
 }
 }

Move Rows

List {
 ForEach(data, id: \.self) { datum in
 Text(datum).font(Font.system(size: 24)).padding()
 }
 .onMove(perform: moveRow)
 } 

func moveRow(from indexes: IndexSet, to destination: Int) {
 if let first = indexes.first {
 data.insert(data.remove(at: first), at: destination)
 }
 } 

Scrollview 
You can set the direction of a ScrollView to be vertical or horizontal.
Note : A Scrollview with a ForEach view is similar to a List. But be warned, the rows are not reusable. It is best to limit the number of rows for memory and performance considerations.

ScrollView { 
}

Modifiers

Text modifiers :

Font style
.font(.largeTitle) // Use a font modifier to make text large
.font(.title) // Set to be the second largest font.

Font weight
.fontWeight(.thin) 

Font design
.font(Font.system(size: 36, design: .serif)) 

Formatting
Text("Italic").italic() 

Allows Tightening
Text("Allows tightening to allow text to fit in one line.")
 .allowsTightening(true) 

Minimum Scale Factor
Text("Sometimes you want to shrink text if long")
 .lineLimit(1)
 .minimumScaleFactor(0.5) 

Line Spacing
Text("SwiftUI offers a Line Spacing modifier for situations where you want to increase the
space between the lines of text on the screen.")
 .lineSpacing(16.0) // Add spacing between lines

Alignment
Text("By default, text will be centered within the screen. But when it wraps to multiple
lines, it will be leading aligned by default. Use multilineTextAlignment modifier to change
this!")
 .multilineTextAlignment(.center) // Center align

Truncation Mode
Text("When text gets truncated, you can control where the elipsis (...) shows.")
 .truncationMode(.middle) // Truncate in middle

Combining Text
Group {
 Text("You can ")
 + Text("format").bold()
 + Text (" different parts of your text by using the plus (+) symbol.")
} 
.font(.title)
.padding(.horizontal)
.layoutPriority(1) 
//Although you see I’m wrapping my Text views in a Group, it is not required. I only do this so I can apply common
modifiers to everything within the Group. See section on the Group view for more information

Baseline Offset
Text("100").bold()
 + Text(" SWIFTUI ")
 .font(Font.system(size: 60))
 .fontWeight(.ultraLight)
 .foregroundColor(.blue)
 .baselineOffset(-12) // Negative numbers go down
 + Text ("VIEWS")

Custom Fonts
Text("Gill Sans")
 .font(Font.custom("Gill Sans", size: 26)) 
//Use a font that already exists on the system. If the font name doesn't exist, it goes back
to the default font.

Foreground Color :

.foregroundColor(Color.gray)

Background Color:

.background(Color.blue)

Frame :

frame(width: 80, height: 150)
.frame(maxWidth: .infinity) // Extend until it hits its parent’s frame

Corner Radius :

.cornerRadius(10)
.cornerRadius(.infinity) // Infinity will always give you the perfect corner no matter the size of the view.

Overlay : 

.overlay(
 Image(systemName: "arrow.up.left")
 .padding() // Add spacing around the symbol
 , alignment: .topLeading) // Align within the layer

Border :

.border(Color.orange) // Create a 2 point border using the color specified 

.stroke(Color.blue, lineWidth: 1)) 
.overlay(
        RoundedRectangle(cornerRadius: 10) // Create the shape
        .stroke(Color(.sRGB, red: 150/255, green: 150/255, blue: 150/255, opacity: 0.1), lineWidth: 1)
      )
// to avoid border cliping while corner radius  
.background(
          RoundedRectangle(cornerRadius: 10) // Create the shape 
          .stroke(Color(.sRGB, red: 150/255, green: 150/255, blue: 150/255, opacity: 0.1), lineWidth: 1) 
     )

Padding:

.padding(2) // Add space all around text text
.padding(.bottom, 15)  // bottom padding
.padding(.horizontal) 

Offset:  (To change position)

.offset(x: -5, y: -5)

Layout Priority

Text("Short description") 
    .layoutPriority(1)  // Truncate last

Layers (overlay and opacity)

Screenshot 2020-01-21 at 11.03.35 AM.png

Shapes

.background(
 RoundedRectangle(cornerRadius: 20) // Create the shape
 .foregroundColor(Color.blue)) // Make shape blue

LineLimit

.lineLimit(1) // Don't let text wrap

FixedSize

Text("ant long text".")
 .fixedSize(horizontal: false, vertical: true) 
// Using fixedSize is another way to get text not to truncate.

Safe Area

.edgesIgnoringSafeArea(.all) 

edgesIgnoringSafeArea() modifier ensures the color goes right to the edge of the screen.

Underline (to underline text)

.underline()

Shadow

.shadow(color: Color.purple, radius: 20, y: 5) // See more info in section on Shadows

Clipped

.clipped()

Blur

.blur(radius: 3))

Accent Color

.accentColor(.yellow)

disabled

.disabled(disabled) // Don't allow to edit when disabled

List Style
with the grouped list style that the rows don’t continue past the last one.

.listStyle(GroupedListStyle()) 

NavigationBar title

// This creates a title in your nav bar
 .navigationBarTitle(Text("Navigation Views")) 
//The navigationBarTitle goes INSIDE the NavigationView, not on it. Notice the default style of the title is large.

NavigationBarHidden

 @State private var isHidden = true
// Hide when the Toggle is on
 .navigationBarHidden(isHidden) 

NavigationBarItems

.navigationBarItems(
 // Button on the leading side
 leading:
 Button(action: { }) {
 Image(systemName: "bell.fill")
 }.accentColor(.pink)
 // Button on the trailing side
 , trailing:
 Button("Actions", action: { })
 .accentColor(.pink)
 ) 

Attributes

spacing : add spacing between inner view (Verically in VStack and Horizontally in HStack)

VStack(spacing: 20) {

 Text("Title")
 .font(.largeTitle)

 Text("Subtitle")
 .font(.title) // Set to be the second largest font.
 .foregroundColor(Color.gray) // Change text color to gray.
 } 

Alignment : 

 VStack(alignment: .leading/.top/.trailing/.bottom)

Previews :

// Xcode looks for PreviewProvider struct
struct YourView_Previews: PreviewProvider {
 // It will access this property to get a view to show in the Canvas (if the Canvas
is shown)
 static var yourview: some View {
 // Instantiate and return your view inside this property to see a preview of it
 YourView()
 }
}

Dark Mode

struct YourView_Previews: PreviewProvider {
 static var yourview: some View {
 YourView()
 // Use the environment function and pass in a property key path and a value
to set that property to.
 .environment(\EnvironmentValues.colorScheme, ColorScheme.dark)
 }
}

Multiple preview

One of the many benefits of SwiftUI is that we get instant previews of our layouts as we work. Even better, we can customize those previews so that we can see multiple designs side by side, see how things look with a navigation view, try out dark mode, and more.

For example, this creates a preview for ContentView that shows three different designs side by side: extra large text, dark mode, and a navigation view:

#if DEBUG
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
                .environment(\.sizeCategory, .accessibilityExtraExtraExtraLarge)
            ContentView()
                .environment(\.colorScheme, .dark)
            NavigationView {
                ContentView()
            }
        }
    }
}
#endif
Screenshot 2020-01-20 at 12.04.29 PM

Specific device

Just look at your list of simulators and type in exactly as you see them displayed in the list.

YourView()
 .yourview(PreviewDevice(rawValue: "iPad Pro (9.7-inch)")) 

Size category

YourView()
 .environment(\.sizeCategory, .accessibilityExtraExtraExtraLarge)
 /*
 Options:
 case accessibilityExtraExtraExtraLarge
 case accessibilityExtraExtraLarge
 case accessibilityExtraLarge
 case accessibilityLarge
 case accessibilityMedium
 case extraExtraExtraLarge
 case extraExtraLarge
 case extraLarge
 case extraSmall
 case large
 case medium
 case small
 */

Preview width height

YourView()
 .previewLayout(PreviewLayout.fixed(width: 896, height: 414)) 

Notes:

1. The body property can only return one view. You will get an error if you try to return more than one view in the body property.
2. Should return view of same type

Screenshot 2020-01-20 at 11.36.26 PM

3. The frame modifier is now the first modifier. In SwiftUI, the order of modifiers matter.

Text("Short description of what I am demonstrating goes here.")
 .frame(maxWidth: .infinity) // Extend until you can't go anymore.
 .font(.title)
 .foregroundColor(Color.white)
 .background(Color.blue) 

4. Code reusability

Screenshot 2020-01-21 at 12.09.06 AM

5. Combine text views

You can create new text views out of several small ones using +, which is an easy way of creating more advanced formatting. For example, this creates three text views in different colors and combines them together:

struct ContentView: View {
    var body: some View {
        Text("Colored ")
            .foregroundColor(.red)
        +
        Text("SwifUI ")
            .foregroundColor(.green)
        +
        Text("Text")
            .foregroundColor(.blue)
    }
}

6. Create custom modifiers

If you find yourself regularly repeating the same set of view modifiers – for example, making a text view have padding, be of a specific size, have fixed background and foreground colors, etc – then you should consider moving those to a custom modifier rather than repeating your code.

For example, this creates a new PrimaryLabel modifier that adds padding, a black background, white text, a large font, and some corner rounding:

struct PrimaryLabel: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.black)
            .foregroundColor(.white)
            .font(.largeTitle)
            .cornerRadius(10)
    }
}

You can now attach that to any view using .modifier(PrimaryLabel()), like this:

struct ContentView: View {
    var body: some View {
        Text("Hello World")
            .modifier(PrimaryLabel())
    }
}

7. Textfield

struct ContentView: View {
    // 1.
    @State var name: String = ""
    var body: some View {
        VStack {
            // 2.
            TextField(" Enter some text", text: $name)
                .border(Color.black)
            Text("Text entered:")
            // 3.
            Text("\(name)")
        }
        .padding()
        .font(.title)
    }
}

1. A State property is declared which will represent the entered text inside the textfield
2. A textfield is displayed with a placeholder text. A border modifier is added to make it clear where the boundary of the textfield is.
3. The entered text will be displayed inside the text view.

8. Prototype with constant bindings

If you’re just trying out a design and don’t want to have to create bindings to use things like text fields and sliders, you can use a constant binding instead. This will allow you to use the object with a realistic value.

For example, this creates a text field with the constant string “Hello”:

TextField("Example placeholder", text: .constant("Hello"))
    .textFieldStyle(RoundedBorderTextFieldStyle())

And this creates a slider with a constant value of 0.5:

Slider(value: .constant(0.5))

9. Create common code for reuse

//create reusable section header text of large title and gray color 

@State private var textFieldData = "This is a text field"

 var body: some View {
 Form {
    Section(header: SectionHeader(name: "Controls in a Form")) {
      Text("This will give you an idea of how different controls are rendered in 
           .a  Form.")
     }
   }
}
struct SectionHeader: View {
 var name: String

 var body: some View {
 Text(name)
 .font(.largeTitle)
 .foregroundColor(Color.gray)
 }
}

10. Conditions in Swift UI

1. Screenshot 2020-01-22 at 4.07.31 PM.png

2. Custom Placeholder/Hint Text logic

ZStack(alignment: .leading) {
 // Only show custom hint text if there is no text entered
 if textFieldData.isEmpty {
 Text("Enter name here").bold()
 .foregroundColor(Color(.systemGray4))
 }
 TextField("", text: $textFieldData)
 } 

Resources

Mix SwiftUI and UIKit, don’t worry you no need really to say goodbye to UIKit you can mix that two using UIHostingController.

Lifecycle – https://medium.com/flawless-app-stories/the-simple-life-cycle-of-a-swiftui-view-95e2e14848a2

SWIFT UI

Diff between group and section

If you want your form to look different when split its items into chunks, you should use the Section view instead. This splits your form into discrete visual groups, just like the Settings app does: (else use Group) Groups don’t actually change the way your user interface looks, they just let us work around SwiftUI’s limitation of ten child views inside a parent – that’s text views inside a form, in this instance.

Form {
    Section {
        Text("Hello World")
    }

    Section {
        Text("Hello World")
        Text("Hello World")
    }

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s