SwiftUI — เฟรมเวิร์กแบบประกาศของ Apple สำหรับสร้างส่วนติดต่อผู้ใช้บนทุกแพลตฟอร์มในระบบนิเวศ เปิดตัวที่ WWDC 2019 แตกต่างจาก UIKit เชิงคำสั่งที่มี viewDidLoad และการอัปเดตหน้าจอด้วยตนเอง SwiftUI อธิบาย UI เป็นชุดของโครงสร้างง่าย ๆ ที่สอดคล้องกับโปรโตคอล View ตามข้อมูลจาก Swift.org (2025) พบว่า SwiftUI ถูกใช้ใน 65% ของโปรเจกต์ใหม่ที่เผยแพร่บน App Store เฟรมเวิร์กจัดการอัปเดตอินเทอร์เฟซโดยอัตโนมัติผ่านกลไก State และ Data Flow — เมื่อข้อมูลเปลี่ยนแปลง View จะถูกวาดใหม่โดยไม่ต้องเรียก reloadData ด้วยตนเอง
ประเด็นสำคัญ
SwiftUI — เฟรมเวิร์ก UI แบบประกาศของ Apple ที่แตกต่างจาก UIKit อย่างสิ้นเชิง แทนที่จะสร้างคอนโทรลเลอร์ วิว และจัดการวงจรชีวิตด้วยตนเอง นักพัฒนาจะอธิบายอินเทอร์เฟซเป็นคำประกาศ: สิ่งที่ควรอยู่บนหน้าจอ ไม่ใช่วิธีสร้างมัน SwiftUI ใช้หลักการของปฏิกิริยา: อินเทอร์เฟซเป็นฟังก์ชันของสถานะ เมื่อสถานะเปลี่ยนแปลง SwiftUI จะคำนวณ body ของ Views ทั้งหมดที่ขึ้นต่อกันโดยอัตโนมัติและอัปเดตเฉพาะส่วนที่เปลี่ยนแปลงของหน้าจอ SwiftUI พร้อมใช้งานบน iOS 13+, iPadOS 13+, macOS 10.15+, watchOS 6+, tvOS 13+ และ visionOS 1+ โค้ด SwiftUI เป็นข้ามแพลตฟอร์ม: ไฟล์เดียวทำงานบน iPhone, iPad, Mac และ Apple Watch โดยมีการปรับเปลี่ยนตามแพลตฟอร์มน้อยที่สุด ตามข้อมูลจาก Apple WWDC Session 101 (2024) SwiftUI ครอบคลุมมากกว่า 90% ของรูปแบบ UI มาตรฐานของ App Store
UIKit — เฟรมเวิร์กเชิงคำสั่ง (2008): นักพัฒนาสร้าง UIViewController กำหนดค่า subviews ใน viewDidLoad ใช้งาน delegate/datasource สำหรับ UITableView และอัปเดตหน้าจอผ่าน reloadData หรือ setNeedsLayout SwiftUI แทนที่คอนโทรลเลอร์ด้วยโครงสร้าง View ง่าย ๆ ตัวแทนด้วย bindings และ onChange Auto Layout ด้วย HStack/VStack/ZStack และตัวปรับแต่ง (padding, frame, offset) UIKit ต้องการการจัดการหน่วยความจำด้วยตนเองผ่าน ARC; SwiftUI ใช้โครงสร้างที่ไม่ต้องการการนับอ้างอิง ประสิทธิภาพของ SwiftUI เทียบได้กับ UIKit: เฟรมเวิร์กใช้อัลกอริทึม diffing สำหรับชุดการเปลี่ยนแปลงที่น้อยที่สุด ใน IT Sectr ใช้ SwiftUI สำหรับโปรเจกต์ใหม่ที่มีเป้าหมาย iOS 17+; โปรเจกต์ที่รองรับ iOS 14–15 ต้องการ UIKit เนื่องจากความเข้ากันได้ที่จำกัดของ SwiftUI
ใน SwiftUI อินเทอร์เฟซถูกอธิบายผ่าน ViewBuilder — result builder ที่แปลงชุดของ Views เป็น tuple หรือ Group ตัวปรับแต่ง (.padding(), .font(), .foregroundColor()) สร้าง Views ใหม่ด้วยการตั้งค่าที่ถูกปรับเปลี่ยนแทนที่จะเปลี่ยนวัตถุดั้งเดิม ตัวปรับแต่งแต่ละตัวจะคืนค่า View ใหม่ ทำให้สามารถต่อเนื่องกันได้ ViewBuilder รองรับ if/else, switch, ForEach — การแสดงผลแบบมีเงื่อนไขและแบบวนรอบโดยไม่ต้องมีคอนโทรลเลอร์แยกต่างหาก View ใน SwiftUI เป็น value type (struct) ซึ่งรับประกันพฤติกรรมที่คาดเดาได้และกำจัดสภาวะการแข่งขัน
View — โปรโตคอลที่มีข้อกำหนดเดียว: computed property body ชนิด some View แต่ละโครงสร้างที่สอดคล้องกับ View จะอธิบายส่วนของหน้าจอใน body ชนิด some View เป็นชนิดส่งกลับแบบทึบแสงที่ซ่อนชนิดรูปธรรมของ View ที่ถูกส่งกลับ (การซ้อน VStack, HStack, ZStack, Text, Image ฯลฯ) คอมไพเลอร์ของ Swift อนุมานชนิดรูปธรรมในเวลาคอมไพล์ รักษาประสิทธิภาพของการเรียกโดยตรงโดยไม่ต้องลบชนิด
import SwiftUI
struct GreetingView: View {
var name: String
var body: some View {
VStack(spacing: 12) {
Text("สวัสดี \(name)!")
.font(.largeTitle)
.foregroundColor(.primary)
Text("ยินดีต้อนรับสู่ SwiftUI")
.font(.body)
.foregroundColor(.secondary)
}
.padding()
.background(
RoundedRectangle(cornerRadius: 12)
.fill(.ultraThinMaterial)
)
}
}โครงสร้าง GreetingView รับพารามิเตอร์ name และแสดงบล็อกข้อความสองบล็อกในสแต็กแนวตั้ง ตัวปรับแต่ง .font, .foregroundColor, .padding และ .background กำหนดค่าลักษณะที่ปรากฏ SwiftUI เรียก body ทุกครั้งที่พารามิเตอร์นำเข้า (name) เปลี่ยนแปลง — การวาดใหม่เกิดขึ้นเฉพาะส่วนที่เปลี่ยนแปลงเท่านั้น ตัวอย่างใช้ RoundedRectangle กับ .ultraThinMaterial — พื้นหลังเบลอแบบเนทีฟที่สร้างไว้ใน SwiftUI
@State — property wrapper ที่ประกาศสถานะท้องถิ่นที่เป็นของ View เดียว SwiftUI จัดการหน่วยความจำ State โดยอัตโนมัติ: เมื่อค่าเปลี่ยน body จะถูกวาดใหม่ แต่เฉพาะสำหรับ Views ที่ใช้ State นั้น State เป็น source of truth สำหรับชนิดง่าย ๆ (String, Int, Bool, enum) อย่าใช้ @State สำหรับโมเดลข้อมูลที่ซับซ้อน — ให้ใช้ @StateObject และ @ObservedObject แทน State ควรเป็น private และถูกเก็บไว้ภายใน View เอง ไม่ควรส่งต่อระหว่างคอมโพเนนต์
import SwiftUI
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack(spacing: 20) {
Text("นับ: \(count)")
.font(.system(size: 48, weight: .bold))
Button(action: { count += 1 }) {
Label("เพิ่ม", systemImage: "plus.circle")
}
.buttonStyle(.borderedProminent)
}
.padding()
}
}ค่าเริ่มต้นของ count = 0 ทุกครั้งที่กดปุ่ม count จะเพิ่มขึ้น; SwiftUI จะวาด CounterView ทั้งหมดใหม่โดยอัตโนมัติ (ทุก Views) ใน UIKit สถานการณ์ที่คล้ายกันจะต้องใช้ IBOutlet, IBAction และการอัปเดต label.text ด้วยตนเอง @State รับประกันว่า View จะถูกวาดใหม่เฉพาะเมื่อ State ที่เฉพาะเจาะจงเปลี่ยนแปลง — อัลกอริทึม diffing ของ SwiftUI จะพบการเปลี่ยนแปลงน้อยที่สุดในทรี
@Binding — property wrapper ที่สร้างการเชื่อมต่อสองทางระหว่าง View และข้อมูลที่ View ไม่ได้เป็นเจ้าของ Binding เป็นการอ้างอิงถึง State (หรือ source of truth อื่น) ที่ช่วยให้ View ลูกสามารถอ่านและเปลี่ยนค่าที่เก็บไว้ในพ่อแม่ได้ Binding แสดงด้วยคำนำหน้า $: $count ส่ง Binding<Int> ไปยัง View ลูก หากไม่มี Binding View ลูกจะไม่สามารถเปลี่ยนข้อมูลของพ่อแม่ได้ — สามารถอ่านได้เท่านั้น
import SwiftUI
struct StepperControl: View {
@Binding var value: Int
let range: ClosedRange<Int>
var body: some View {
HStack {
Button(action: { if value > range.lowerBound { value -= 1 } }) {
Image(systemName: "minus.circle")
}
Text("\(value)")
.frame(minWidth: 40)
Button(action: { if value < range.upperBound { value += 1 } }) {
Image(systemName: "plus.circle")
}
}
}
}
struct ParentView: View {
@State private var quantity = 5
var body: some View {
StepperControl(value: $quantity, range: 1...10)
}
}ParentView เป็นเจ้าของ State quantity และส่ง Binding ผ่าน $quantity StepperControl สามารถเปลี่ยนค่าได้ และ quantity ในพ่อแม่จะซิงค์โดยอัตโนมัติ Binding ไม่ใช่สำเนาของข้อมูล แต่เป็นสะพานไปยัง source of truth ใช้ @Binding สำหรับคอนโทรลแบบกำหนดเอง ตัวแก้ไข และคอมโพเนนต์ที่ใช้ซ้ำได้ซึ่งต้องเปลี่ยนข้อมูลของพ่อแม่
@StateObject — property wrapper สำหรับสร้างและเป็นเจ้าของอินสแตนซ์ของคลาสที่สอดคล้องกับ ObservableObject View สร้างวัตถุหนึ่งครั้งต่อวงจรชีวิตและถูกวาดใหม่เมื่อคุณสมบัติ @Published ของมันเปลี่ยนแปลง @ObservedObject — wrapper ที่คล้ายกัน แต่ View ไม่ได้เป็นเจ้าของวัตถุ — วัตถุถูกสร้างและเก็บไว้นอก View (ส่งผ่านตัวเริ่มต้น) Apple แนะนำ @StateObject สำหรับ source of truth ในลำดับชั้นของ View และ @ObservedObject สำหรับการฉีดการพึ่งพา
import SwiftUI
import Combine
class UserSettings: ObservableObject {
@Published var username: String = "Guest"
@Published var isLoggedIn = false
}
struct ProfileView: View {
@StateObject private var settings = UserSettings()
var body: some View {
VStack {
TextField("Username", text: $settings.username)
.textFieldStyle(.roundedBorder)
Toggle("Logged In", isOn: $settings.isLoggedIn)
if settings.isLoggedIn {
Text("ยินดีต้อนรับ \(settings.username)!")
.font(.headline)
}
}
.padding()
}
}UserSettings — ObservableObject ที่มีสองคุณสมบัติ @Published ProfileView เป็นเจ้าของวัตถุผ่าน @StateObject การเปลี่ยนแปลง username หรือ isLoggedIn จะวาด ProfileView ใหม่โดยอัตโนมัติ @Published ใช้ Combine Publisher เพื่อแจ้ง SwiftUI เกี่ยวกับการเปลี่ยนแปลง หากต้องการส่ง settings ไปยัง Views ลูก ให้ใช้ @ObservedObject:
Apple กำหนดสี่ระดับของ Data Flow ใน SwiftUI: @State (ท้องถิ่น, value type), @Binding (สองทาง), @StateObject/@ObservedObject (reference type กับ ObservableObject), @EnvironmentObject (ทั่วโลก, ฉีดผ่านสภาพแวดล้อม) EnvironmentObject ช่วยให้ส่งข้อมูลผ่านลำดับชั้น View ทั้งหมดโดยไม่ต้องส่งอย่างชัดเจนในตัวเริ่มต้น นอกจากนี้ @AppStorage ทำงานกับ UserDefaults, @SceneStorage กับสถานะฉาก, @FetchRequest กับ Core Data การเลือกระดับของ Data Flow กำหนดสถาปัตยกรรมของแอปพลิเคชัน: หน้าจอธรรมดาใช้ State/Binding, แบบโมดูลใช้ ObservedObject, ขนาดใหญ่ใช้ EnvironmentObject + โซลูชันแบบ Redux (TCA, Composable Architecture)
| Property Wrapper | ความเป็นเจ้าของ | ชนิด | เมื่อใดควรใช้ |
|---|---|---|---|
| @State | ท้องถิ่น | Value (struct, enum) | สถานะง่ายของ View เดียว (ตัวนับ, สวิตช์, ฟิลด์ข้อความ) |
| @Binding | ภายนอก | การอ้างอิงถึง State | View ลูกเปลี่ยนข้อมูลของพ่อแม่ |
| @StateObject | View เป็นเจ้าของ | Reference (class) | Source of truth สำหรับโมเดลข้อมูลที่ซับซ้อน |
| @ObservedObject | การฉีด | Reference (class) | โมเดลที่สร้างภายนอก View (ส่งผ่าน init) |
| @EnvironmentObject | ทั่วโลก | Reference (class) | ข้อมูลที่พร้อมใช้งานสำหรับลำดับชั้นทั้งหมด (auth, theme) |
คำถามที่พบบ่อย
@State — สำหรับ value types (struct, enum, String, Int) และสถานะท้องถิ่นของ View เดียว SwiftUI จัดการหน่วยความจำ State โดยอัตโนมัติ @StateObject — สำหรับ reference types (class) ที่สอดคล้องกับ ObservableObject @StateObject เป็นเจ้าของวัตถุและวาด View ใหม่เมื่อคุณสมบัติ @Published เปลี่ยนแปลง สำหรับตัวนับธรรมดาให้ใช้ @State; สำหรับโมเดลที่มีตรรกะทางธุรกิจให้ใช้ @StateObject
ได้ SwiftUI ผสานรวมกับ UIKit ผ่าน UIHostingController (SwiftUI ภายใน UIKit) และ UIViewRepresentable (UIKit ภายใน SwiftUI) UIHostingController ห่อ SwiftUI View ใน UIViewController UIViewRepresentable ช่วยให้ใช้คอมโพเนนต์ UIKit (MKMapView, WKWebView) ใน SwiftUI นี่เป็นวิธีมาตรฐานสำหรับการโยกย้ายโปรเจกต์จาก UIKit ไปยัง SwiftUI
ViewBuilder — result builder (Swift 5.1) ที่แปลงชุดของ Views เป็นค่าเดียวชนิด TupleView, Group หรือ ConditionalContent ViewBuilder ช่วยให้เขียน if/else และ switch เชิงคำสั่งภายใน body แบบประกาศ หากไม่มี ViewBuilder คุณจะต้องส่งคืน AnyView หรือ Group สำหรับแต่ละบล็อกเงื่อนไข ViewBuilder เป็นเหตุผลที่ body ไม่ต้องใช้เครื่องหมายจุลภาคระหว่าง Views
ได้ SwiftUI รองรับ iOS 13+, iPadOS 13+, macOS 10.15+, watchOS 6+, tvOS 13+ และ visionOS 1+ อย่างไรก็ตาม API บางอย่างใช้ได้เฉพาะในเวอร์ชันใหม่กว่าเท่านั้น: ตัวอย่างเช่น navigationStack (iOS 16+), Observable macro (iOS 17+) สำหรับความเข้ากันได้ย้อนหลัง ให้ใช้ #available และการปรับเปลี่ยนของ UIKit
Xcode Debug View Hierarchy แสดงทรีของ SwiftUI View พร้อมตัวปรับแต่งและเฟรม เครื่องมือ SwiftUI Inspector (แผงด้านขวาของ Xcode) ช่วยให้ปรับเปลี่ยนตัวปรับแต่งแบบเรียลไทม์ self._printChanges() ใน body จะบันทึกสาเหตุของการวาดใหม่ Instruments กับเทมเพลต SwiftUI จะติดตามประสิทธิภาพของ View และระบุการวาดใหม่ที่มากเกินไป
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ