Building a Structured Card View with Data Model, List, Navigation, and Modal Sheets in SwiftUI
This tutorial demonstrates how to create a reusable CardView component in SwiftUI, define a data model, populate a List with ForEach, add a NavigationStack with custom buttons, and implement modal sheet presentation and dismissal using state bindings and environment variables.
In the previous chapter a CardView component was created using component‑based programming, allowing global style changes by editing the component itself. This section reviews that benefit and proceeds to a more structured approach.
Data Model Construction – A new Swift file Model.swift is added, importing SwiftUI and defining a struct that conforms to Identifiable :
import SwiftUI struct Model: Identifiable {
var id = UUID()
var platformIcon: String
var title: String
var platformName: String
var indexURL: String
}Because the struct adopts Identifiable , each instance receives a unique id , enabling the list to differentiate duplicate entries.
Example data is then created as an array of Model instances:
// MARK: 示例数据
var models = [
Model(platformIcon: "icon_juejin", title: "移动端签约作者", platformName: "稀土掘金技术社区", indexURL: "https://juejin.cn/user/3897092103223517"),
Model(platformIcon: "icon_csdn", title: "iOS创作者", platformName: "CSDN博客", indexURL: ""),
Model(platformIcon: "icon_aliyun", title: "专家博主", platformName: "阿里云社区", indexURL: ""),
Model(platformIcon: "icon_huaweiyun", title: "专家博主", platformName: "华为云社区", indexURL: "")
]With the model and sample data ready, the UI can be built.
Card List with List and ForEach – In ContentView the original hard‑coded cards are removed and replaced by a List that iterates over models :
List {
ForEach(models) { item in
CardView(platformIcon: item.platformIcon, title: item.title, platformName: item.platformName, indexURL: item.indexURL)
}
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
}The list row background is set to transparent and the default separator is hidden to achieve a cleaner appearance.
Navigation Menu – A NavigationStack (iOS 16+) or NavigationView (earlier versions) wraps the entire view, providing a title and a trailing “add” button:
NavigationStack {
// view content
.navigationBarTitle("文如秋雨", displayMode: .inline)
.navigationBarItems(trailing: addBtn())
}The addBtn() function returns a button with a plus‑circle icon:
func addBtn() -> some View {
Button(action: { }) {
Image(systemName: "plus.circle.fill")
.font(.system(size: 17))
.foregroundColor(.blue)
}
}Modal Sheet for Adding a Card – Tapping the add button toggles a @State Boolean showNewView , which drives a .sheet presentation of a new NewView :
@State var showNewView: Bool = false
.sheet(isPresented: $showNewView) {
NewView()
}The NewView itself contains its own navigation bar and a close button:
struct NewView: View {
var body: some View {
NavigationStack {
Text("Hello, World!")
.navigationBarTitle("添加身份卡", displayMode: .inline)
.navigationBarItems(trailing: closeBtn())
}
}
func closeBtn() -> some View {
Button(action: { }) {
Image(systemName: "xmark.circle.fill")
.font(.system(size: 17))
.foregroundColor(.gray)
}
}
}Closing the Modal – Two approaches are shown: (1) passing a @Binding Boolean to NewView and toggling it, and (2) using the environment variable @Environment(.presentationMode) to dismiss the sheet directly:
// Binding method
@Binding var showNewView: Bool
self.showNewView.toggle()
// Environment method
@Environment(.presentationMode) var presentationMode
self.presentationMode.wrappedValue.dismiss()Both methods are valid; the environment approach avoids the need for explicit bindings.
Chapter Summary – The chapter covered constructing a data model, using List and ForEach to render reusable card views, adding a navigation stack with custom titles and buttons, and presenting/dismissing modal sheets. These patterns are common in iOS apps such as todo lists, news feeds, and video card galleries, and set the stage for future features like adding, editing, and deleting cards.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.