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 August 23, 2021

Dynamic List – SwiftUI Notes #39

SwiftUI

Contents

  • Chuẩn bị
  • Dynamic List
    • Hiển thị array String
    • Sử dụng List
    • Sử dụng kiểu dữ liệu phức tạp
  • Identifiable Protocol
  • NavigationView
    • Cú pháp
    • NavigationLink
  • Master – Detail
    • Detail View
    • Master View
  • Tạm kết

Chào mừng bạn đến với Fx Studio. Chúng ta đã bắt đầu với việc hiển thị một danh sách với dữ liệu tĩnh tại bài viết trước rồi. Bài viết này, sẽ tiếp tục hướng dẫn bạn hiển thị một danh sách động cùng với List. Chủ đề hôm nay là Dynamic List, bài viết thuộc series SwiftUI Notes.

Nếu bạn chưa biết về cách hiển thị một danh sách đơn giản, thì có thể tham khảo link dưới đây:

    • Simple List

Còn nếu mọi việc đã ổn rồi, 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

(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ề mặt demo, bạn chỉ cần thực hiện demo trên các SwiftUI View đơn giản. Mình sẽ thực hiện các View riêng biệt với nhau, nên bạn không cần lo lắng gì nhiều về tính liên kết của các View trong một Project. Về mặt giao diện thì khá là đơn giản à.

(Hoặc bạn có thể checkout project demo tại đây.)

Dynamic List

Chúng ta đã có bài viết hướng dẫn về cách hiển thị một danh sách với List & ForEach rồi. Tuy nhiên, chúng chỉ là cách hiển thị với các dữ liệu tĩnh hay cố định. Hoặc các dữ liệu tăng tuyến tính mà thôi.

Trong dự án thực tế, chúng ta không thể nào hiển thị dữ liệu như vậy được. Với ứng dụng mobile, sẽ luôn nhận được một danh sách với dữ liệu lấy về từ API. Chúng ta không biết trước được việc hiển thị chúng sẽ là bao nhiêu phần tử, hay cụ thể các phần tử sẽ như thế nào …

Do đó, chúng ta sẽ phải làm được việc là hiển thị một Array đối tượng lên List hay ForEach. Đây sẽ là tiền đề cho bạn trong các bài viết sau, với dữ liệu từ API hay từ DataBase.

Hiển thị array String

Chúng ta sẽ bắt đầu từ cái đơn giản trước. Đó là bạn sẽ hiển thị một Array String lên giao diện. Array String này được xem là phần dữ liệu cho View.

Ví dụ ta có 1 array như sau:

let cities = ["Hà Nội",
              "Hải Phòng",
              "Vinh",
              "Huế",
              "Đà Nẵng",
              "Nha Trang",
              "Hồ Chí Minh",
              "Vũng Tàu"]

Giải pháp đầu tiên sẽ là sử dụng ForEach, vì nó có index. Từ index bạn sẽ lấy được dữ liệu từng phần tử trong Array. Bạn xem tiếp ví dụ nha:

var body: some View {
    ScrollView {
        ForEach(0..<cities.count) { index in
            Text(cities[index])
        }
    }
}

Đó là ưu điểm của ForEach đem lại cho bạn. Khi chúng ta có được index để duyệt các phần tử trong một mãng. Bạn bấm Resume và xem kết quả nhoé.

Dynamic List

Kết quả nhìn không được đẹp cho lắm nhĩ. Chúng ta sẽ cải thiện nó tiếp thôi.

Sử dụng List

Phiên bản trên nhìn hơi xấu, nó không đem lại cho chúng ta cảm giác là một danh sách hay UITableView như với UIKit. Để cải thiện thì bạn sẽ sử dụng List, mặc dù chúng ta sẽ không có được index như ForEach.

Nhưng với control này, SwiftUI hỗ trợ thêm nhiều tham số có ý nghĩa cao hơn. Chúng ta sẽ chỉnh sửa lại code trên với List như sau:

var body: some View {
    List(cities, id: \.self) { city in
        Text(city)
    }
}

Trong đó:

  • cities là phần dữ liệu cho List
  • id làm tham số được thêm vào để xác định mỗi phần tử trong cities là duy nhất.
  • Vì phần tử của cities là một String, mà không phải là một cấu trúc dữ liệu phức tạp. Do đó, ta sẽ lấy keypath chính là bản thân nó self để làm id

Để dễ hiểu hơn, thì trước đây chúng ta gọi id là identifier cho các định danh của UITableViewCell của UIKit. Bấm Resume và xem kết quả như thế nào nhoé.

Dynamic List

Bây giờ thì trông đẹp rồi đó, đã có trải nghiệm giống UITableView lúc xưa rồi. Ahihi!

Sử dụng kiểu dữ liệu phức tạp

Chúng ta lại tiếp tục tìm một giải pháp toàn diện hơn. Bắt đầu, chúng ta sẽ định nghĩa lại kiểu dữ liệu cần dùng cho List. Sử dụng struct để tạo ra một kiểu dữ liệu phức tạp hơn.

Ví dụ với struct Weather như sau:

struct Weather {
    enum WeatherStatus {
        case sun
        case rain
        case cloud
    }
    
    var city: String
    var country: String
    var temperature: Int
    var status: WeatherStatus
    
    func getStatusInfo() -> String {
        var str = ""
        
        switch status {
        case .sun:
            str = "sun.max.fill"
        case .rain:
            str = "cloud.bolt.rain"
        case .cloud:
            str = "cloud.fill"
        }
        
        return str
    }
}

Chúng ta sẽ dùng nó làm dữ liệu cho các phần tiếp theo của bài viết.

Tiếp theo, bạn sẽ custom một SwiftUI View để phục vụ cho việc hiển thị được đẹp hơn. Xem ví dụ nha:

struct WeatherRow: View {
    var weather: Weather
    
    var body: some View {
        HStack {
            Image(systemName: weather.getStatusInfo())
                .resizable()
                .padding(.all)
                .frame(width: 80.0, height: 80.0)
                .aspectRatio(contentMode: .fill)
            VStack(alignment: .leading) {
                Text(weather.city)
                    .font(.title)
                Text(weather.country)
                    .fontWeight(.thin)
            }
            Spacer()
            Text("\(weather.temperature)°C")
                .font(.largeTitle)
                .fontWeight(.bold)
                .multilineTextAlignment(.center)
                .padding(.all)
        }
    }
}

Cũng không quá phức tạp, bạn xem Preview của nó như sau. Bạn hãy tạo 1 đối tượng Weather để truyền vào Preview nhoé.

Cuối cùng bạn cần tạo mới một SwiftUI View để hiển thị danh sách thời tiết cho các thành phố nhoé. Xem ví dụ sau:

struct WeatherList: View {
    var weathers = Weather.dummyData()
    
    var body: some View {
        List(weathers, id: \.city) { item in
            WeatherRow(weather: item)
        }
    }
}

Trong đó:

  • weathers là một Array với kiểu là Weather.
  • dummyData() để sinh ra dữ liệu cho thuộc tính, bạn tự viết phần này nhoé. Còn không thì tham khảo source code của repo
  • Sử dụng id với key path là thuộc tính city

Kết quả sẽ trông đẹp mắt hơn nhiều so với 1 Array String truyền thống. Xem nhoé!

Dynamic List

Identifiable Protocol

Ở phần trên, ta sử dụng tới id hay là một Identifier để định danh các phần tử là duy nhất trong một Array. Tuy nhiên, chúng vẫn có thể trùng lặp, nếu thuộc tính bạn lựa chọn là id có thể xảy ra trùng lặp với nhau về giá trị.

Do đó, Swift cung cấp cho chúng ta một Protocol là Identifiable. Sẽ giúp cho class/struct bạn có thêm được tính năng định danh riêng biệt. Điều này giúp các phần tử trong Array sẽ là duy nhất.

public protocol Identifiable {

    /// A type representing the stable identity of the entity associated with
    /// an instance.
    associatedtype ID : Hashable

    /// The stable identity of the entity associated with this instance.
    var id: Self.ID { get }
}

@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
extension Identifiable where Self : AnyObject {

    /// The stable identity of the entity associated with this instance.
    public var id: ObjectIdentifier { get }
}

Việc kế thừa Identifiable Protocol cho các struct/class, thì cần bạn khai báo thêm thuộc tính id. Với kiểu dữ liệu cho id đảm bảo được là một Hashable value.

Chúng ta sẽ nâng cấp struct Weather trên với việc sử dụng Identifiable Protocol.

struct Weather: Identifiable {
    
    enum WeatherStatus {
        case sun
        case rain
        case cloud
    }
    
    var id = UUID()
    var city: String
    var country: String
    var temperature: Int
    var status: WeatherStatus
    
    func getStatusInfo() -> String {
        var str = ""
        
        switch status {
        case .sun:
            str = "sun.max.fill"
        case .rain:
            str = "cloud.bolt.rain"
        case .cloud:
            str = "cloud.fill"
        }
        
        return str
    }
}

Trong đó:

  • Chúng ta implement thêm Identifiable Protocol cho Weather
  • Với Identifiable Protocol, cần phải có thêm một thuộc tính là id. Và bạn sẽ phải khai báo thêm thuộc tính đó.
  • id sẽ phải là các giá trị mà đảm bảo chúng là duy nhất.
  • Sử dụng UUID() để có được một chuỗi String & đảm bảo là duy nhất

Cuối cùng, bạn chỉ cần edit nhẹ phần List lại như sau:

struct WeatherList: View {
    
    var weathers = Weather.dummyData()
    
    var body: some View {
        List(weathers) { item in
            WeatherRow(weather: item)
        }
    }
}

Lúc này, code của chúng ta nhìn đẹp hơn nhiều rồi. Và khi đối tượng chúng ta là một Identifiable, thì không cần tới tham số id như ở trên.

Bạn hãy bấm Resume và cảm nhận kết quả nhoé!

NavigationView

A view for presenting a stack of views representing a visible path in a navigation hierarchy.

Chúng ta đã có được một Dynamic List hiển thị một danh sách với dữ liệu động cho giao diện rồi. Để trải nghiệm được đẹp hơn nữa, ta sẽ sử dụng thêm một NavigationView cho giao diện của chúng ta. Vì mục đích của chúng ta không chỉ đơn giản là xem 1 danh sách với các phần tử của nó mà thôi.

Mục đích chính sẽ là điều hướng từ List sang các View khác.

Đây là phiên bản của UINavigationViewController từ UIKit cho SwiftUI.

Cú pháp

Chúng ta đi sơ qua các cú pháp cơ bản nhất của một NavigationView trước nhoé. Tạo một NavigationView:

NavigationView { 
        List {
              Text("Hello World")
        }
      .navigationBarTitle(Text("Navigation Title")) // Default to large title style
}

Trong đó:

  • NavigationView đóng vài trò là root của View
  • Chúng ta sẽ đưa vào trong nó các SwiftUI View cần thiết, trong ví dụ là một List

Tiếp theo, bạn muốn thay đổi title của NavigationView, bạn sẽ sử dụng modifier .navigationBarTitle(_:_:) . Ví dụ như sau:

.navigationBarTitle(Text("Navigation Title"), displayMode: .inline)

Bạn có thể chọn các kiểu hiển thị của Title với tham số cho displayMode.

Cuối cùng, bạn có thể thêm các BarButtonItem cho NavigationBar. Xem ví dụ nha:

//barbuttonitems. : traing & leading
.navigationBarItems(trailing:
       Button(action: {
    // Add action
       }, label: {
    Text("Add")
       })
)

Xem lại cập nhật cho WeatherList của chúng ta với NavigationView nhoé!

struct WeatherList: View {
    
    var weathers = Weather.dummyData()
    
    var body: some View {
        NavigationView {
            List(weathers) { item in
                WeatherRow(weather: item)
            }
            .navigationBarTitle(Text("Weather List"))
            .navigationBarItems(trailing:
                                    Button(action: {
                                        //action
                                    }, label: {
                                        Text("Add")
                                    })
            )
        }
        
    }
}

Bấm Resume và cảm nhận kết quả nhoé!

Dynamic List

NavigationLink

Quan trọng nhất khi sử dụng NavigationView đó là sự điều hướng. Các để bạn đưa một View khác vào bằng NaviagtionView đó là sử dụng đối tượng NaviagtionLink.

Sẽ có nhiều điều bất ngờ tại đây khi bạn từ UIKit chuyển sang SwiftUI nhoé.

Trong đó cần:

  • destination là điểm đến. Chính là View của bạn muốn đưa vào.
  • label chính là nội dung của bạn cần hiển thị cho NavigationLink.

Xem ví dụ với WeatherList của chúng ta nhoé.

struct WeatherList: View {
    
    var weathers = Weather.dummyData()
    
    var body: some View {
        NavigationView {
            List(weathers) { item in
                NavigationLink(
                    destination: WeatherDetail(),
                    label: {
                        WeatherRow(weather: item)
                    })
            }
            .navigationBarTitle(Text("Weather List"))
        }
        
    }
}

Trong đó, ta sử dụng NavigationLink để bọc các WeatherRow lại với nhau. Mỗi khi kích vào Row thì sẽ được di chuyển sang màn hình WeatherDetail.

Kết quả sẽ như thế này!

Dynamic List

Sẽ có một cái dấu > nhỏ nhỏ đó bạn à. Nhìn cũng đẹp và tương đối ổn đó.

Chúng ta sẽ gặp lại NavigationView ở các phần sau hoặc 1 chương khác. Ở đây chỉ là giới thiệu sơ bạn để sử dụng trong List mà thôi.

Master – Detail

Đây là mẫu design được sử dụng khá nhiều trong các ứng dụng mobile. Với List sẽ là một Master View. Dùng chính điều hướng của NavigationView để chuyển sang các màn hình Detail View.

Detail View

Ở ví dụ trên, bạn có điểm đích của NavigationView là một WeatherDetail. Đó chính là màn hình cho Detail trong mô hình Master – Detail của chúng ta.

Lúc này, ta sẽ design lại WeatherDetail như sau, bạn tham khảo code nhoé.

struct WeatherDetail: View {
    var weather: Weather
    
    var body: some View {
        VStack(alignment: .center) {
            CircleImage(name: "img1")
            HStack {
                Image(weather.getStatusInfo())
                    .padding(.all)
                    .frame(width: 80.0, height: 80.0)
                    .aspectRatio(contentMode: .fill)
                VStack(alignment: .leading) {
                    Text(weather.city)
                        .font(.title)
                    Text(weather.country)
                        .fontWeight(.thin)
                }
                Spacer()
                Text("\(weather.temperature)°C")
                    .font(.largeTitle)
                    .fontWeight(.bold)
                    .multilineTextAlignment(.center)
                    .padding(.all)
            }
            Spacer()
            }.navigationBarTitle(Text(weather.city), displayMode: .inline)
    }
}

Trong đó:

  • Chúng ta có dữ liệu của màn hình là một thuộc tính weather. Sẽ được truyền từ List vào.
  • Bạn cần custom view cho Image, trong ví dụ mình tạo một custom view là CircleImage. Giúp cho ảnh của bạn được bo tròn và bóng đổ.

Xem kết quả nhoé!

Mình có thay đổi lại icon cho các biểu tượng thời tiết, để nhìn cute hơn. Ahihi!

Master View

Master View trong mô hình Master – Detail là một danh sách. Tại đó:

  • Điều hướng sang các Detail View khác nhau
  • Lưu trữ dữ liệu tập trung
  • Chuyển dữ liệu cho các Detail View

Trong ví dụ của chúng ta thì đã sử dụng List làm Master View rồi. Nhiệm vụ cuối cùng là kết nối và truyền dữ liệu sang Detail View thôi. Xem ví dụ code nhoé.

List(weathers) { item in
    NavigationLink(
        destination: WeatherDetail(weather: item),
        label: {
            WeatherRow(weather: item)
        })
}
.navigationBarTitle(Text("Weather List"))

Bạn chỉ cần quan tâm tới dòng code có WeatherDetail(weather: item) là xong. Tất cả chỉ có như vậy mà thôi. Bạn hãy bấm Preview và test lại các View của chúng ta nhoé. Chúc bạn thành công!

Tạm kết

  • Hiển thị một danh sách với dữ liệu động từ một array
  • Các cách tạo các Identifier từ các đối tượng trong array
  • Custom một class/struct với Identifiable Protocol để sử dụng làm dữ liệu cho List
  • Cơ bản về NavigationView & NavigationLink
  • Mô hình Master – Detail trong SwiftUI

 

Okay! Tới đây, mình xin kết thúc bài viết về Dynamic List 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!

FacebookTweetPinYummlyLinkedInPrintEmailShares7

Related Posts:

  • dart
    List type - Dart Tour
  • feature_bg_swiftui_7
    Giới thiệu MapKit trên SwiftUI
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 AI api AppDistribution autolayout basic ios tutorial blog ci/cd closure collectionview combine concurrency crashlytics dart dart basic dart tour Declarative delegate deploy design pattern fabric fastlane firebase flavor flutter GCD gradients iOS MVVM optional Prompt engineering protocol Python rxswift safearea Swift Swift 5.5 SwiftData SwiftUI SwiftUI Notes tableview testing TravisCI unittest

Recent Posts

  • Vibe Coding là gì?
  • Cách Đọc Sách Lập Trình Nhanh và Hiệu Quả Bằng GEN AI
  • Nỗ Lực – Hành Trình Kiến Tạo Ý Nghĩa Cuộc Sống
  • Ai Sẽ Là Người Fix Bug Khi AI Thống Trị Lập Trình?
  • Thời Đại Của “Dev Tay To” Đã Qua Chưa?
  • Prompt Engineering – Con Đường Để Trở Thành Một Nghề Nghiệp
  • Vấn đề Ảo Giác (hallucination) khi tương tác với Gen AI và cách khắc phục nó qua Prompt
  • Điều Gì Xảy Ra Nếu… Những Người Dệt Mã Trở Thành Những Người Bảo Vệ Cuối Cùng Của Sự Sáng Tạo?
  • Khi Cô Đơn Gặp Python
  • Học vì tồn tại

You may also like:

  • List type - Dart Tour
    dart
  • Giới thiệu MapKit trên SwiftUI
    feature_bg_swiftui_7

Archives

  • April 2025 (1)
  • March 2025 (8)
  • January 2025 (7)
  • December 2024 (4)
  • September 2024 (1)
  • July 2024 (1)
  • June 2024 (1)
  • May 2024 (4)
  • April 2024 (2)
  • March 2024 (5)
  • January 2024 (4)
  • 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 (43)
  • Code (10)
  • Combine (22)
  • Flutter & Dart (24)
  • iOS & Swift (101)
  • No Category (1)
  • RxSwift (37)
  • SwiftUI (80)
  • Tutorials (86)

Newsletter

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

    Copyright © 2025 Fx Studio - All rights reserved.