Skip to content
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Fx Studio
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Written by chuotfx on June 4, 2021

Input Controls – SwiftUI Notes #29

SwiftUI

Contents

  • Chuẩn bị
  • 1. SecureField
    • 1.1. Login form
    • 1.2. Edit password field
  • 2. Button
    • 2.1. Cú pháp
    • 2.2. Make color Button
    • 2.3. Actions
  • 3. Reacting to inputs
    • 3.1. View Model
    • 3.2. Config
    • 3.3. Validation
    • 3.4. Action & State
  • 4. Toggle Control
  • 5. Slider
  • 6. Stepper
  • Tạm kết

Chào bạn đến với Fx Studio. Chúng ta đã tìm hiểu lần lượt các UI Control cơ bản (Text, Image, TextField) của SwiftUI. Và tiếp nối series SwiftUI Notes này, chúng ta sẽ tìm hiểu một loạt các UI Control cơ bản khác. Chúng thuộc nhóm các Control giúp người dùng tương tác hay nhập dữ liệu từ giao diện màn hình … hay còn gọi là Input Controls.

Để biết chúng là gì, thì …

Bắt đầu thôi!

Chuẩn bị

Về mặt tool và version, các bạn tham khảo như sau:

    • SwiftUI 2.0
    • Xcode 12

Về mặt kiến thức, bạn cần biết trước các kiến thức cơ bản với SwiftUI & SwiftUI App. Tham khảo các bài viết sau, nếu bạn chưa đọc qua SwiftUI:

    • Làm quen với SwiftUI
    • Cơ bản về ứng dụng SwiftUI App

Về mặt demo, hầu như là demo đơn giản, vì tập trung vào từng View riêng lẻ. Do đó, bạn cũng không cần phải quá lo lắng và việc tạo mới project cũng không ảnh hưởng gì nhiều. Ngoài ra, bạn có thể checkout mã nguồn của các bài viết tại đây.

(Mặc định, mình xem như bạn đã biết về cách tạo project với SwiftUI & SwiftUI App rồi.)

Về demo cho bài Input Controls này, chúng ta sẽ thử tạo một trang Login đơn giản. Mình sẽ xây dựng trang Login hoàn thiện đầy theo từng bước một. Còn sau đây là code ví dụ ban đầu của nó, để cho bạn có thể tham khảo.

struct DemoLoginView: View {

    var body: some View {
        VStack {
            // Welcome
            HStack(alignment: .center) {
                Image("logo")
                    .resizable()
                    .frame(width: 100, height: 100)
                    .clipShape(Circle())
                    .padding()
                VStack(alignment: .leading) {
                    Text("Welcome to")
                        .font(.headline)
                    Text("Fx Studio")
                        .font(.largeTitle)
                        .fontWeight(.bold)
                }
            }
        }
        .padding()
    }
}

Chúng ta sử dụng các đối tượng Image & Text đơn giản, nên cũng không có gì quá phức tạp ở. Còn đây là hình ảnh ban đầu cho nó.

Input Controls

1. SecureField

1.1. Login form

Chúng ta sẽ dùng các HStack & VStack để tách ra các khối giao diện. Nhằm mục đích quản lý có được tốt hơn. Công việc tiếp theo của bạn làm tiếp trang demo này chính à thêm các TextField cho Login.

Ta sẽ thêm 2 thuộc quản lý cho thông tin đăng nhập, đó là username & password. Bạn tham khảo code sau:

@State var username: String = ""
@State var password: String = ""

Trong đó:

  • @State giúp thuộc tính này có thể lưu trữ được và cập nhật giá trị từ View hay các View khác ở bên ngoài.
  • Kiểu dữ liệu sử dụng là String

Tiếp theo, bạn sẽ thêm đoạn code này vào VStack của body ở trên (ở dưới đoạn Welcome). Code như sau:

VStack {
    Text("Login with your account.")
        .italic()
    TextField("Username", text: $username)
        .kute()
    TextField("Password", text: $password)
        .kute()
    Divider()
}
.padding([.top, .bottom], 20.0)

Bạn cần áp dụng hết tinh hoa của bài trước:

  • Các bố cục View
  • Sử dụng các modifier cơ bản như .padding
  • Sử dụng lại Custom Style & Modifier (từ bài trước)

Kết quả như sau:

1.2. Edit password field

Có gì đó sai sai ở form đăng nhập trên. Trường password của bạn như vậy thì không ổn. Chúng ta cần che chúng đi.

Tất nhiên, SwiftUI cũng đã hỗ trợ bạn điều này. Có một hay khá hay là SwiftUI đã tách biệt giữ Field bình thường và Field cần bảo mật. Và nó được gọi là SecureField.

Đặc trưng:

  • Tương tự như TextField
  • Dùng để che đi các văn bản khi nhập vào

Cú pháp:

@State var password = ""
...

SecureField.init("Password", text: $password)
  .textFieldStyle(RoundedBorderTextFieldStyle())
  • Khi chưa nhập vào Field thì sẽ hiện ra placeholder
  • Còn khi có kí tự được nhập thì chúng sẽ được hiển thị bằng dấu tròn đen

Cuối cùng, bạn chỉnh sửa lại form ở trên nha.

TextField("Username", text: $username)
    .kute()
SecureField("Password", text: $password)
    .kute()

Bấm Live Preview lại và tận hưởng kết quả nhoé!

2. Button

2.1. Cú pháp

Một trong số những UI Input Controls phổ biến tiếp theo đó là Button. Nó giúp cho người dùng có thể tương tác với giao diện ứng dụng. Thông qua các hành động chạm vào, bấm vào, tap vào … Hiểu nôm na

Nhận sự kiện từ người dùng.

Có nhiều phiên bản khởi tạo với Button và cũng có nhiều thay đổi từ SwiftUI 1.0 & 2.0. Mình giới thiệu cho bạn phiên bản ổn nhất sau đây:

struct Button<Label> where Label : View

Ta xem qua hàm khởi tạo của nó thì như sau:

init(
  action: @escaping () -> Void,
  @ViewBuilder label: () -> Label
)

Trong đó:

  • action chịu trách nhiệm xử lý khi người dùng có tác động vào Button
  • label là nội dung của Button. Nó là kiểu @ViewBuidler nên bạn có thể tuỳ ý sáng tạo vào đó.

Như vậy là ổn cho Button rồi. Bạn sẽ thêm 2 Button vào trong code demo thôi. Bạn thêm đoạn code này vào tiếp form Login của chúng ta.

HStack {
      Button(action: {
 //...
      }, label: {
          Text("Login")
      })

      Button(action: {
 //...
      }, label: {
          Text("Clear")
      })

Chúng ta sẽ có 2 button cho 2 mục đích

  • Login dùng để xử lý đăng nhập
  • Clear dùng để xoá hết các giá trị nhập ở 2 Field

Bấm Resume để xem kết quả nào.

2.2. Make color Button

Mặc định với Button là như vậy, chúng sẽ nhận các style cơ bản và hiển thị theo đúng nền tảng quy định. Tất nhiên, chúng ta không thể nào làm ngơ đc. Cần phải make color cho chúng nó được đẹp hơn.

Có nhiều phương pháp, mình sẽ chọn phương pháp Custom ViewModifier để có thể áp dụng cho nhiều Button một lúc.

Ưu điểm phương pháp này, nhanh gọn lẹ và áp dụng được cho nhiều đối tượng.

Bắt đầu, bạn có thêm một struct cho một ViewModifier mới. Mình đặt tên là KuteButtonViewModifier. Code tham khảo như sau:

  • Mình có trình bày các Custom ViewModifier ở bài viết TextField, bạn tìm và xem lại nếu quên hoặc chưa biết.
  • Ngoài ra, bạn có thể áp dụng cách Custom Style cho riêng Button. Nó sẽ oke hơn cách này nhiều.
struct KuteButtonViewModifier: ViewModifier {
    
    func body(content: Content) -> some View {
        content
            .padding(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
            .background(Color.black)
            .cornerRadius(8)
            .shadow(color: Color.gray.opacity(1.0), radius: 3, x: 1, y: 2)
    }
}

Bước tiếp theo, bạn cần tạo thêm extension cho các SwiftUI View. Cũng tiếp tục tham khảo code nhoé!

extension View {

        // ....
        
    func kuteButton() -> some View {
        ModifiedContent(
            content: self,
            modifier: KuteButtonViewModifier()
        )
    }
}

Cũng khá là EZ, bạn sử dụng đối tượng ModifiedContent để áp dụng các thay đổi. Và đặt tên function là kuteButton nó cũng là tên của modifier mà bạn sử dụng bên ngoài.

Cuối cùng, chúng ta xem việc áp dụng style cho 2 Button sẽ như thế nào.

HStack {
    Button(action: {

    }, label: {
        Text("Login")
            .fontWeight(.bold)
            .foregroundColor(.white)
    })
    .kuteButton()

    Button(action: {

    }, label: {
        Text("Clear")
            .fontWeight(.bold)
            .foregroundColor(.white)
    })
    .kuteButton()
}

Trong đó:

  • Ở mỗi Button, bạn chỉ cần gọi .kuteButton là được
  • Bạn chỉ cần thêm một chú màu sắc cho các Text là đẹp ngay

Xem kết quả nhoé!

Input Controls

Simple is the best!

2.3. Actions

Sau khi đã make color cho Button rồi. Bạn sẽ đến phần xử lý sự kiện cho Input Controls này. Bạn có thể lợi dụng closure của tham số action để code vào đó.

Tuy nhiên, bạn có thể gọi một function khác vào đó, giúp cho việc code trở nên đơn giản và đẹp hơn. Chúng ta tạm thời thêm 2 function sau.

func login() {
    // ahihi
}

func clear() {
    username = ""
    password = ""
}

Trong đó:

  • login() dành cho Button Login và sẽ handle sau
  • clear() dành cho Button Clear. Chỉ cần gán lại giá trị của 2 thuộc tính username & password thì 2 TextField sẽ tự động reset

Còn sau đây là cách bạn cập nhật 2 function đó vào 2 Button. Chỉ sửa lại đoạn code cho 2 Button.

HStack {
    Button(action: login, label: {
        Text("Login")
            .fontWeight(.bold)
            .foregroundColor(.white)
    })
    .kuteButton()

    Button(action: clear, label: {
        Text("Clear")
            .fontWeight(.bold)
            .foregroundColor(.white)
    })
    .kuteButton()
}

Nhìn gọn và đẹp hơn rồi đó. Ahihi!

3. Reacting to inputs

Phần này sẽ gọi là viết vì đam mê chứ nó cũng không liên quan gì nhiều tới UI Control lắm. Mình sẽ đi nhanh phần này. Cũng hi vọng các bạn vô tình đọc được tới đây sẽ tăng thêm niềm tin và động lực để học SwiftUI. Vì, mình sẽ tiến hành kết hợp

SwiftUI + MVVM + Combine

Chúng ta sẽ áp dụng mô hình quản lý MVVM vào project SwiftUI này. Chính xác hơn là cho mỗi SwiftUI View này. Đây là việc áp dụng hạn chế chứ không phải full cả mô hình. Để giúp bạn khỏi kì vọng và khỏi ảo tưởng sức mình của bản thân quá nhiều. Mình sẽ liệt kê ra những gì lấy được từ tinh hoa MVVM vào SwiftUI.

  • Áp dụng Combine để thực hiện Binding 2 chiều cho View & ViewModel (quan trọng nhất)
  • Lợi dụng các wrapper properties như @Published để phát đi các trạng thái state của ViewModel
  • Loại bỏ đi các subscriptions vì chúng không cần thiết khi mọi thứ đã là Declarative Programming
  • Ràng buộc dữ liệu giữa View & ViewModel theo nguyên tắc nguồn sự thật chân lý. Mọi thứ sẽ tự động và cập nhật. Giá trị sẽ lưu trữ tại một nơi duy nhất.

3.1. View Model

Okay! Chúng ta bắt đầu tạo một class ViewModel cho demo thôi. Tiết kiệm thời gian thì bạn xem code ban đầu như sau:

import Foundation
import SwiftUI

class DemoLoginViewModel : ObservableObject {
    
    // 1
    @Published var username: String = ""
    @Published var password: String = ""
    
    // 2
    @Published var alert: Bool = false
    @Published var isLogined: Bool = false
    @Published var message: String = ""
    
    // 3
    func checkValid() -> Bool {
        return !username.isEmpty && !password.isEmpty
    }
    
    // 4
    func reset() {
        username = ""
        password = ""
    }
    
    func login() {
        if username == "admin" && password == "123" {
            alert = true
            isLogined = true
            message = "Login successful."
        } else {
            alert = true
            isLogined = false
            message = "Username or password is incorrect."
        }
    }
    
}

Trong đó:

  • Class phải kế thừa lại protocol ObservableObject, để giúp thể hiện của nó có thể sử dụng được với nguyên tắc Declaring Data trong SwiftUI với kiểu tham chiếu.
  • Sử dụng các wrapper properties là @Published để giúp vừa lưu trữ và vừa phát đi được giá trị ra bên ngoài.
  • Class sẽ hard code khá nhiều. Bạn sẽ phải đăng nhập username là admin và password là 123

Mình chia ra làm 4 nhóm trong class này:

  1. Các thuộc tính cần thiết để binding data 2 chiều từ View. Làm username & password
  2. Các thuộc tính mang tính chất là trigger để giúp thông báo trạng thái của ViewModel. View sẽ phản ứng theo các trạng thái đó.
  3. Kiểm tra validation cho 2 thuộc tính username & password. Trường hợp này chỉ kiểm tra có rỗng hay là không.
  4. Nhóm actions từ View yêu cầu ViewModel thực hiện. Đó là reset và login

3.2. Config

Bạn sẽ tiến hành sử dụng ViewModel vào trong SwiftUI View, bắt đầu bằng việc thêm 1 thuộc tính mới cho View như sau:

@StateObject var viewmodel = DemoLoginViewModel()

Sử dụng @StateObject cho kiểu dữ liệu tham chiếu. Để biến viewmodel thành nguồn sự thật chân lý. Nơi tập trung dữ liệu và cập nhất tới các view có ràng buộc dữ liệu với nó.

Một số cách khác có thể dùng là Binding hay Environment. Tất cả đều được, miễn là bạn thấy thoải mái với cách nào thôi.

Chúng ta sẽ thực hiện một số thay đổi ở View, vì chúng ta không còn sử dụng các thuộc tính trực tiếp từ View nữa. Mà giờ đây sẽ dùng các thuộc tính của viewmodel cho View.

  • Cho 2 TextField, bạn vẫn cần thêm $ trước viewmodel để giúp có thể thay đổi dữ liệu thuộc tính từ bên View
VStack {
    Text("Login with your account.")
        .italic()
    TextField("Username", text: $viewmodel.username)
        .kute()
    SecureField("Password", text: $viewmodel.password)
        .kute()
    Divider()
}
.padding([.top, .bottom], 20.0)
  • Cho 2 Button
HStack {
    Button(action: viewmodel.login, label: {
        Text("Login")
            .fontWeight(.bold)
            .foregroundColor(.white)
    })
    .kuteButton()
    .disabled(!viewmodel.checkValid())

    Button(action: viewmodel.reset, label: {
        Text("Clear")
            .fontWeight(.bold)
            .foregroundColor(.white)
    })
    .kuteButton()
}

Tạm thời như vậy là ổn rồi. Bây giờ chúng ta đi vào 2 công việc cần kiểm tra trên giao diện.

3.3. Validation

Vì 2 TextField đã liên kết với viewmodel qua các thuộc tính của chính ViewModel rồi. Do đó, bạn không cần bận tâm về việc cập nhật dữ liệu. Bên cạnh đó function checkValid() của ViewModel đã đảm nhận công việc đó rồi.

Chúng hoạt động như thế nào?

Với SwiftUI sẽ hoạt động như sau:

  • Khi View đã ràng buộc với Data, mọi thay đổi từ View sẽ ảnh hưởng tới Data. Bao gồm các thuộc tính hay các function tính toán
  • ViewModel lúc này được xem là nguồn dữ liệu duy nhất. Nhận mọi cập nhật từ View bên ngoài. Bên cạnh đó sẽ phát lại giá trị mới cho các View update lại giao diện
  • Lúc đó 2 TextField nhập và kéo theo 2 thuộc tính của ViewModel sẽ thay đổi theo. Và function checkVaild() cũng sẽ thay đổi kết quả dựa vào giá trị 2 thuộc tính.

Bạn chú ý tới modifier của Button .disabled(!viewmodel.checkValid()). Nó đang được ràng buộc với function checkValid() của ViewModel. Nên khi giá trị của function đó thay đổi, cũng sẽ ảnh hưởng tới chính Button đó.

Như vậy, bạn hoàn toàn an tâm khi người dùng chưa nhập hoặc bỏ sót 1 trong 2 Field thì Button của chúng ta sẽ không kích được. Mọi thứ tiếp theo đó sẽ không hoạt động.

Để cho Button thêm màu sắc thì chúng ta cập nhật lại Custom View Modifier của nó như sau:

struct KuteButtonViewModifier: ViewModifier {
    
    var isDisabled = false
    
    func body(content: Content) -> some View {
        content
            .padding(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
            .background(isDisabled ? Color.gray : Color.black)
            .cornerRadius(8)
            .shadow(color: Color.gray.opacity(1.0), radius: 3, x: 1, y: 2)
    }
}


extension View {    
    func kuteButton(isDisabled: Bool = false) -> some View {
        ModifiedContent(
            content: self,
            modifier: KuteButtonViewModifier(isDisabled: isDisabled)
        )
    }
}

Trong đó:

  • Nếu disable thì background sẽ là màu gray
  • Ngược lại là enable thì background sẽ là màu back

Sau đó bạn cập nhật lại Button với tham số cho modifier. Bạn tham khảo code như sau:

Button(action: viewmodel.login, label: {
    Text("Login")
        .fontWeight(.bold)
        .foregroundColor(.white)
})
.kuteButton(isDisabled: !viewmodel.checkValid())
.disabled(!viewmodel.checkValid())

Bấm Live Preview và cũng nhau xem kết quả nhoé!

3.4. Action & State

Bây giờ, chúng ta sang phần xử lý sự kiện người dùng. Mà cập nhật lại trạng thái giao diện thông qua kết quả xử lý kia. Trong demo, ta có 2 sự kiện chính là login & clear.

Bạn bắt đầu trước với clear

  • Tại Button Clear
Button(action: viewmodel.reset, label: {
    Text("Clear")
        .fontWeight(.bold)
        .foregroundColor(.white)
})
.kuteButton()

Sự kiện được truyền trực tiếp từ Button tới viewmodel và sẽ gọi function reset .

  • Tại function reset
func reset() {
    username = ""
    password = ""
}

Bạn sẽ thấy, chúng ta chỉ tương tác phần Data thôi. Không cần phải call back hay delegate gì như với UIKit. Do các thuộc tính trên được khai báo với @Published rồi. Nên các View sẽ tự động cập nhật theo giá trị mới của chúng

Tiếp theo là sự kiện login. Sự kiện này sẽ phức tạp hơn một chút. Và trước tiên bạn xem function login ở ViewModel có gì đã.

func login() {
    if username == "admin" && password == "123" {
        alert = true
        isLogined = true
        message = "Login successful."
    } else {
        alert = true
        isLogined = false
        message = "Username or password is incorrect."
    }
}

Bạn chỉ cần chú ý tới 2 thuộc tính, alert & isLogined. Nó cũng được khai báo tương tự như username & password. Nhưng trong demo nó lại mang ý nghĩa khác

  • Lưu giữ trạng thái của ViewModel
  • Đối với View thì nó giống như trigger, dùng để kích hoạt việc thay đổi giao diện ở View

Sử dụng chúng như thế nào. Bạn xem code ví dụ sau ở View.

VStack {
   // bla bla bla
}
.alert(isPresented: $viewmodel.alert, content: {
            Alert(title: Text(viewmodel.isLogined ? "Fx Studio" : "Error"),
                  message: Text(viewmodel.message),
                  dismissButton: .default(Text("Got it!"))
            )
        })

Trong đó:

  • VStack là đối tượng View lớn nhất
  • .alert sẽ hiển thị ra một Alert. Cái này sẽ xuất hiện khi viewmodel.alert = true
  • Với keyword $ trước viewmodel để giúp cho việc thay đổi giá trị từ View xảy ra. Bạn không cần quan tâm tới việc xét lại giá trị cho viewmodel.alert

Nhiệm vụ chính là kích hoạt trạng thái để cho View hiển thị Alert tương ứng với các tường hợp lỗi hay thành công từ ViewModel.

input controls

DONE!

4. Toggle Control

Okay! Ta đã áp dụng cơ bản của cơ bản MVVM vào SwiftUI nhằm sử dụng tối đa sức mạnh của các đối tượng View cơ bản rồi. Ngoài ra, vẫn có nhiều đối tượng View khác. Ta sẽ khám phá thêm 1 tí trong số UI Input Controls.

Đây là phiên bản UISwitch cho SwiftUI. Chúng cho sẽ có một UI Input Controls giúp cho người dùng chọn 1 trong 2 giá trị (true hoặc false).

  • Khởi tạo
    • Bạn cần cung cấp cho nó 1 biến có khả năng Binding là được
    • Kiểu dữ liệu sẽ là Boll
public init(
  isOn: Binding<Bool>,
  @ViewBuilder label: () -> Label
)
  • Sử dụng vào demo
HStack {
    Spacer()
    Toggle(isOn: $viewmodel.rememberUser) {
        Text("Remember me")
            .font(.subheadline)
            .foregroundColor(.gray)
    }
    .fixedSize()
}

Bạn tạo thêm một thuộc tính rememberUser ở ViewModel nữa. Với kiểu dữ liệu là Bool & khai báo với @Published. Cuối cùng, bạn bấm Live Preview để xem kết quả nào!

Input Controls

5. Slider

Đọc qua tên bạn cũng biết nó là gì rồi. Đây là phiên bản của UISlider từ UIKit và có mặt trên SwiftUI với tên là Slider. Ý nghĩa và công năng sử dụng thì tương tự như UISlider. Nó giúp người có thể chọn ra một giá trị từ biên độ nhỏ tới lớn (tuỳ thuộc vào người dùng gán vào)

Xem qua ví dụ code của Input Controls này để hiểu hơn.

@State var amount: Double = 0

// .....

VStack {
            HStack {
                Text("0")
                Slider(
                    value: $amount,
                    in: 0.0 ... 10.0,
                    step: 0.5
                )
                Text("10")
            }
            Text("\(amount)")
        }
        .padding()

Trong đó:

  • Bạn cần một biến với khai báo @State hoặc @Binding cho tham số value
  • bounds phạm vị biến đổi
  • step là giá trị tăng giảm cho mỗi lần thay đổi
  • onEditingChanged lắng nghe sự kiện mới bắt đầu hoặc kết thúc khi người dùng tương tác

Xem kết quả nhoé!

6. Stepper

Về khái niệm, Stepper tương tự như Slider. Nhưng thay vì kéo thả nhanh, người dùng sẽ nhấn vào 1 trong 2 button. Và mỗi lần nhấn sẽ tăng hoặc giảm lên 1 ít.

Xem nhanh code ví dụ của UI Input Controls này luôn cho máu nhoé.

@State var quantity = 0.0

// ....

 VStack {
     Stepper(
       "Quantity: \(quantity)",
       value: $quantity,
       in: 0 ... 10,
       step: 0.5
     )
 }

Trong đó:

  • title tiêu đề, thường hiển thị giá trị hiện tại
  • value biến binding được
  • bounds phạm vi tăng giảm
  • step là giá trị mỗi bước nhảy
  • onEditingChanged theo dõi việc bắt đầu hay kết thúc của sự kiện người dùng cho đối tượng.

Bấm Live Preview và xem kết quả nhoé!

 

Tạm kết

  • Tìm hiểu được thêm các UI Input Controls phục vụ cho việc lấy dữ liệu từ người dùng và tương tác với người dùng
  • Áp dụng được MVVM & Combine vào trong SwiftUI
  • Xử lý được các yêu cầu đơn giản (validation, submit, state) cho một form

 

Okay! Tới đây, mình xin kết thúc bài viết về đối tượng Input Controls trong SwiftUI. Và nếu có gì thắc mắc hay góp ý cho mình thì bạn có thể để lại bình luận hoặc gởi email theo trang Contact.

  • Bạn có thể checkout code tại đây.
  • Bài viết tiếp theo tại đây.

Cảm ơn bạn đã đọc bài viết này!

FacebookTweetPinYummlyLinkedInPrintEmailShares41

Related Posts:

  • Presenting an Alert - SwiftUI Notes #4
    Presenting an Alert - SwiftUI Notes #4
  • SwiftUI - Phần 6 : Basic UI Controls
    SwiftUI - Phần 6 : Basic UI Controls
  • Creating your UI - SwiftUI Notes #2
    Creating your UI - SwiftUI Notes #2
  • Tích hợp UIViewController (UIKit) vào SwiftUI Project - SwiftUI Notes #14
    Tích hợp UIViewController (UIKit) vào SwiftUI Project -…
Tags: SwiftUI, SwiftUI Notes
Written by chuotfx

Hãy ngồi xuống, uống miếng bánh và ăn miếng trà. Chúng ta cùng nhau đàm đạo về đời, về code nhóe!

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Donate – Buy me a coffee!

Fan page

Fx Studio

Tags

Actor Advanced Swift api AppDistribution Asynchronous autolayout basic ios tutorial blog callback ci/cd closure collectionview combine concurrency CoreData Core Location crashlytics darkmode dart dart basic dart tour Declarative decoding delegate deploy fabric fastlane firebase flavor flutter GCD iOS mapview MVVM optional protocol rxswift Swift Swift 5.5 SwiftUI SwiftUI Notes tableview testing TravisCI unittest

Recent Posts

  • Raw String trong 10 phút
  • Dispatch Semaphore trong 10 phút
  • Tổng kết năm 2022
  • KeyPath trong 10 phút – Swift
  • Make color App Flutter
  • Ứng dụng Flutter đầu tiên
  • Cài đặt Flutter SDK & Hello world
  • Coding Conventions – người hùng hay kẻ tội đồ?
  • Giới thiệu về Flutter
  • Tìm hiểu về ngôn ngữ lập trình Dart

You may also like:

  • Declarative app development - SwiftUI Notes #7
    Declarative app development - SwiftUI Notes #7
  • Tích hợp UIView (UIKit) vào SwiftUI Project - SwiftUI Notes…
    Tích hợp UIView (UIKit) vào SwiftUI Project - SwiftUI Notes #15
  • SwiftUI - Phần 6 : Basic UI Controls
    SwiftUI - Phần 6 : Basic UI Controls
  • Tích hợp UIViewController (UIKit) vào SwiftUI Project -…
    Tích hợp UIViewController (UIKit) vào SwiftUI Project - SwiftUI Notes #14
  • Tích hợp SwiftUI vào UIKit Project - SwiftUI Notes #13
    Tích hợp SwiftUI vào UIKit Project - SwiftUI Notes #13

Archives

  • February 2023 (1)
  • January 2023 (2)
  • November 2022 (2)
  • October 2022 (1)
  • September 2022 (5)
  • August 2022 (6)
  • July 2022 (7)
  • June 2022 (8)
  • May 2022 (5)
  • April 2022 (1)
  • March 2022 (3)
  • February 2022 (5)
  • January 2022 (4)
  • December 2021 (6)
  • November 2021 (8)
  • October 2021 (8)
  • September 2021 (8)
  • August 2021 (8)
  • July 2021 (9)
  • June 2021 (8)
  • May 2021 (7)
  • April 2021 (11)
  • March 2021 (12)
  • February 2021 (3)
  • January 2021 (3)
  • December 2020 (3)
  • November 2020 (9)
  • October 2020 (7)
  • September 2020 (17)
  • August 2020 (1)
  • July 2020 (3)
  • June 2020 (1)
  • May 2020 (2)
  • April 2020 (3)
  • March 2020 (20)
  • February 2020 (5)
  • January 2020 (2)
  • December 2019 (12)
  • November 2019 (12)
  • October 2019 (19)
  • September 2019 (17)
  • August 2019 (10)

About me

Education, Mini Game, Digital Art & Life of coders
Contacts:
contacts@fxstudio.dev

Fx Studio

  • Home
  • About me
  • Contact us
  • Mail
  • Privacy Policy
  • Donate
  • Sitemap

Categories

  • Art (1)
  • Blog (22)
  • Code (4)
  • Combine (22)
  • Flutter & Dart (24)
  • iOS & Swift (86)
  • RxSwift (37)
  • SwiftUI (76)
  • Tutorials (70)

Newsletter

Stay up to date with our latest news and posts.
Loading

    Copyright © 2023 Fx Studio - All rights reserved.

    Share this ArticleLike this article? Email it to a friend!

    Email sent!