SwiftUI เป็นเฟรมเวิร์กแบบประกาศ (declarative) จาก Apple สำหรับสร้างส่วนติดต่อผู้ใช้บนทุกแพลตฟอร์มของระบบนิเวศ แทนที่จะอธิบายขั้นตอนแบบบังคับ (imperative) นักพัฒนาประกาศว่าส่วนติดต่อควรมีลักษณะอย่างไร และ SwiftUI จัดการการเรนเดอร์และอัปเดต ตาม Apple Developer Documentation (2025) SwiftUI รองรับ iOS 15+, iPadOS 15+, macOS 12+, watchOS 8+ และ tvOS 15+ และใช้ View Protocol เป็นส่วนประกอบพื้นฐานสำหรับคอมโพเนนต์อินเทอร์เฟซทั้งหมด
ประเด็นสำคัญ
body เป็นพื้นฐานของคอมโพเนนต์ UI SwiftUI ใด ๆ โดยคืนคำอธิบายหน้าจอผ่านการประกอบวิว (view composition)SwiftUI เป็นเฟรมเวิร์กแบบประกาศที่ Apple เปิดตัวในปี 2019 เพื่อแทนที่ UIKit ในโปรเจกต์ใหม่ แทนที่จะสร้างอินสแตนซ์ UIView ด้วยตนเองและเพิ่มลงในลำดับชั้น นักพัฒนาอธิบายอินเทอร์เฟซผ่านโครงสร้างที่สอดคล้องกับโปรโตคอล View SwiftUI จะคำนวณความแตกต่างระหว่างสถานะปัจจุบันและสถานะใหม่โดยอัตโนมัติและวาดเฉพาะส่วนที่เปลี่ยนแปลงโดยใช้เอนจินเรนเดอร์ของตัวเอง
เฟรมเวิร์กเขียนด้วย Swift โดยใช้ความหมายของค่า (value semantics) ซึ่งก็คือโครงสร้าง ไม่ใช่คลาส ทำให้คอมโพเนนต์ UI มีน้ำหนักเบาและปลอดภัยต่อเธรด แตกต่างจาก UIKit ที่ UIViewController อาจมีขนาด 200+ ไบต์เนื่องจาก Objective-C runtime ส่วน SwiftUI View เป็นเพียงโครงสร้างขนาดไม่กี่ไบต์ สิ่งนี้สำคัญโดยเฉพาะสำหรับ watchOS ที่มีหน่วยความจำจำกัด
คำอธิบาย View เดียวกันทำงานบน iPhone, iPad, Mac, Apple Watch, Apple TV และ Apple Vision Pro SwiftUI ปรับอินเทอร์เฟซให้เข้ากับแพลตฟอร์ม: ท่าทางสัมผัสบน iOS, การกดแป้นพิมพ์บน macOS, การเลื่อน Digital Crown บน watchOS ซึ่งช่วยลดเวลาในการพัฒนาสำหรับบริษัทที่เผยแพร่แอปบนหลายแพลตฟอร์มของ Apple แต่ต้องมีการกำหนดค่าเพิ่มเติมสำหรับองค์ประกอบเฉพาะของแต่ละแพลตฟอร์ม
ใน SwiftUI แต่ละหน้าจอเป็นโครงสร้างที่ใช้โปรโตคอล View โดยมีข้อกำหนดเดียวคือคุณสมบัติที่คำนวณได้ body ชนิด some View คีย์เวิร์ด some (ชนิดโอเพค) ซ่อนชนิดรูปธรรมของวิว ทำให้ SwiftUI สามารถปรับการเรนเดอร์ให้เหมาะสม ภายใน body นักพัฒนาจะรวมคอมโพเนนต์สำเร็จรูป — Text, Image, Button, List — โดยใช้ ViewBuilder ซึ่งประกอบวิวหลายอันเป็นอันเดียว
struct GreetingView: View {
let name: String
var var body: some View {
VStack {
Text("สวัสดี, \(name)!")
.font(.title)
.foregroundColor(.blue)
Image(systemName: "hand.wave")
.imageScale(.large)
}
.padding()
}
}
ในตัวอย่าง VStack (สแต็กแนวตั้ง) ประกอบด้วย Text และ Image ค่าของ name ถูกส่งผ่านตัวเริ่มต้นโครงสร้าง — นี่คือวิธีที่ DI (การฉีดพึ่งพา) ทำงานใน SwiftUI โดยไม่ต้องใช้คอนเทนเนอร์ DI ภายนอก ตัวปรับแต่งแต่ละตัวจะคืนวิวใหม่ที่มีการเปลี่ยนแปลงที่ใช้ โดยไม่เปลี่ยนแปลงต้นฉบับ ซึ่งเป็นไปได้ด้วยความไม่เปลี่ยนเปลี่ยนของชนิดค่า (value types)
ViewBuilder คือ result builder ที่มีคำอธิบาย @resultBuilder ซึ่งประกอบวิวได้สูงสุด 10 อันเป็นอันเดียว ภายใน body สามารถใช้ if/else, switch และ ForEach โดยไม่ต้องใช้ wrapper เพิ่มเติม ForEach ทำงานกับองค์ประกอบ Identifiable — วิวแต่ละอันจะได้รับ id เฉพาะสำหรับแอนิเมชันที่ถูกต้องเมื่อเพิ่ม/ลบ
ใน SwiftUI สถานะเป็นตัวกำหนดว่าเนื้อหาใดจะแสดงบนหน้าจอ เมื่อสถานะเปลี่ยนแปลง SwiftUI จะสร้าง body ของวิวที่พึ่งพาใหม่และเปรียบเทียบผลลัพธ์กับก่อนหน้าโดยใช้อัลกอริทึม diff การจัดเก็บสถานะใช้ property wrappers — แต่ละตัวแก้ไขงานเฉพาะของมัน: สถานะภายในเครื่อง, การเชื่อมต่อกับวิวลูก, หรือโมเดลข้อมูลภายนอก
struct CounterView: View {
@State private var count = 0
var var body: some View {
VStack {
Text("ตัวนับ: \(count)")
Button("เพิ่ม") {
count += 1
}
}
}
}
class UserViewModel: ObservableObject {
@Published var name = ""
@Published var age = 0
}
@State จัดเก็บค่าท้องถิ่นอย่างง่าย (Int, String, Bool) ภายในโครงสร้าง View SwiftUI ย้ายหน่วยความจำจากโครงสร้างไปยังพื้นที่จัดเก็บแยกต่างหาก — ดังนั้นคุณสมบัติที่มี @State สามารถเปลี่ยนแปลงได้แม้ว่า View จะเป็นชนิดค่าก็ตาม @ObservableObject สำหรับคลาสที่มีคุณสมบัติ @Published ซึ่งการเปลี่ยนแปลงจะแจ้งให้ SwiftUI ทราบถึงความจำเป็นในการวาดใหม่โดยอัตโนมัติ
@Binding สร้างการเชื่อมต่อสองทิศทางกับแหล่งข้อมูลที่อยู่ในวิวแม่ แม่ส่ง $variable (ค่าที่ฉาย) และลูกอ่านและเขียนค่าผ่าน binding ซึ่งช่วยให้สามารถย้ายการป้อนข้อความหรือสวิตช์ไปยังคอมโพเนนต์แยกต่างหากในขณะที่เก็บสถานะไว้ในแม่ หากไม่มี @Binding การเปลี่ยนแปลงแต่ละครั้งจะต้องใช้คลอเซอร์ callback เพื่อส่งค่าใหม่ขึ้นไปข้างบน
ก่อน iOS 16 การนำทางใน SwiftUI สร้างขึ้นบน NavigationView — API ดั้งเดิมที่มีพฤติกรรมซับซ้อนบน iPad (มุมมองแยก, สองคอลัมน์) เริ่มตั้งแต่ iOS 16 Apple แนะนำ NavigationStack — ทางเลือกที่เรียบง่ายพร้อมเส้นทางที่ปลอดภัยชนิด นักพัฒนากำหนด enum ของเส้นทางที่เป็นไปได้ และ NavigationStack จัดการสแต็กหน้าจอโดยอัตโนมัติพร้อมรองรับ deep link และกลับไปยังราก
enum Route: Hashable {
case detail(id: Int)
case settings
}
struct ContentView: View {
var var body: some View {
NavigationStack {
List {
NavigationLink("หน้าจอรายละเอียด",
value: Route.detail(id: 42))
NavigationLink("การตั้งค่า",
value: Route.settings)
}
.navigationDestination(for: Route.self) { route in
switch route {
case .detail(let id): DetailView(id: id)
case .settings: SettingsView()
}
}
}
}
}
เส้นทางที่สอดคล้องกับ Hashable อนุญาตให้ใช้ชนิดข้อมูลใด ๆ ในการส่งพารามิเตอร์ navigationDestination(for:destination:) เชื่อมโยงชนิดเส้นทางกับวิวเป้าหมาย ข้อได้เปรียบเหนือการนำทาง UIKit คือไม่ต้องวาดใหม่เมื่อเพิ่มเส้นทางใหม่: เพียงเพิ่ม case ใน enum และ handler ใน switch Deep link จะถูกจัดการผ่าน processDeepLink บน NavigationStack
สำหรับการนำทางแบบโปรแกรม (หลังจากเข้าสู่ระบบ, ตัวจับเวลา หรือการตอบสนองของเซิร์ฟเวอร์) ใช้ @State กับตัวเริ่มต้น NavigationLink: NavigationLink(isActive: $isActive) เมื่อ isActive = true การเปลี่ยนภาพจะเกิดขึ้นโดยไม่ต้องสัมผัสผู้ใช้ ทางเลือกอื่นคือการ bind อาร์เรย์ $path ใน NavigationStack: $path.append(Route.detail(id: 1))
Modifier เป็นเมธอดที่คืนสำเนาที่ถูกปรับเปลี่ยนของวิว แตกต่างจาก UIKit ที่การกำหนดค่าคุณสมบัติทำได้โดยการเปลี่ยนวิวที่มีอยู่ SwiftUI สร้างค่าใหม่ด้วยการเปลี่ยนแปลงที่ใช้ การต่อสายตัวปรับแต่งสร้างอินเทอร์เฟซสุดท้ายจากการแปลงตามลำดับ: แบบอักษร → ระยะห่าง → สี → เงา → ท่าทาง
Apple มีตัวปรับแต่งในตัวมากกว่า 200 ตัว ที่พบบ่อยที่สุด: .font(), .foregroundColor(), .padding(), .background(), .cornerRadius(), .shadow(), .opacity(), .offset() ลำดับของตัวปรับแต่งสำคัญ: .padding() ก่อน .background() จะเติมพื้นที่ด้วยระยะห่าง หลังจากนั้น — เฉพาะพื้นที่ด้านใน ตัวปรับแต่งแบบกำหนดเอง สร้างขึ้นผ่านโปรโตคอล ViewModifier
ตัวปรับแต่งสามารถใช้แบบมีเงื่อนไขผ่านตัวดำเนินการ ternary: .foregroundColor(isError ? .red : .primary) สำหรับแอนิเมชันใช้ .animation(.easeInOut, value: state) — ตัวปรับแต่งแอนิเมชันจะผูกกับคุณสมบัติสถานะเฉพาะ เมื่อคุณสมบัตินี้เปลี่ยนแปลง SwiftUI จะเคลื่อนไหวการเปลี่ยนระหว่างค่าเก่าและค่าใหม่ แอนิเมชัน ทำงานกับ opacity, offset, scale, rotation, ขนาดและสี — แต่ละคุณสมบัติมี AnimatableParameter ที่สอดคล้องกัน
สำหรับแอนิเมชันแบบกำหนดเอง มี .transition (ปรากฏ/หายไป) และ .matchedGeometryEffect (การเปลี่ยนองค์ประกอบอย่างราบรื่นระหว่างสองคอนเทนเนอร์) อย่างหลังใช้สำหรับแอนิเมชัน hero ในรายการ: ไอคอนในเซลล์รายการค่อย ๆ เปลี่ยนเป็นภาพขนาดใหญ่บนหน้าจอรายละเอียด
การเลือกระหว่าง SwiftUI และ UIKit เป็นหนึ่งในปัญหาที่นักพัฒนา iOS ต้องเผชิญเป็นอันดับแรก ทั้งสองเฟรมเวิร์กรองรับโดย Apple แต่แก้ปัญหาการสร้างอินเทอร์เฟซด้วยวิธีที่แตกต่างกันโดยพื้นฐาน: SwiftUI แบบประกาศ, UIKit แบบบังคับ ความแตกต่างปรากฏในการจัดการสถานะ การนำทาง ประสิทธิภาพและความเข้ากันได้
| ด้าน | SwiftUI | UIKit |
|---|---|---|
| แนวทาง | ประกาศ: แสดงอะไร | บังคับ: วิธีสร้าง |
| สถานะ | Property Wrappers, การวาดใหม่อัตโนมัติ | ด้วยตนเอง: reloadData, setNeedsLayout |
| โค้ด UI | กระชับ, สายตัวปรับแต่ง | เยิ่นเย้อ, NSCoder/Storyboard/ข้อจำกัด |
| ประสิทธิภาพ | สูงบน iOS 17+, อัลกอริทึม diff | สูงสุดบน iOS 12–16, ควบคุมโดยตรง |
| รุ่นต่ำสุด | iOS 15+ (รองรับเต็มที่) | iOS 2+ (ทุกรุ่น) |
สำหรับโปรเจกต์ใหม่ที่มีรุ่น iOS 17 ขั้นต่ำ Apple แนะนำ SwiftUI เป็นเฟรมเวิร์กหลัก UIKit ยังคงจำเป็นสำหรับอินเทอร์เฟซที่ต้องการการควบคุมการเรนเดอร์อย่างละเอียด (UICollectionViewLayout แบบกำหนดเอง, ฉาก CAAnimation ที่ซับซ้อน) หรือรองรับ iOS 12–14 หลายโปรเจกต์ใช้แนวทางแบบผสมผสาน: SwiftUI ผ่าน UIHostingController ถูกฝังในแอป UIKit และ UIViewRepresentable อนุญาตให้ใช้คอมโพเนนต์ UIKit ภายในลำดับชั้น SwiftUI
คำถามที่พบบ่อย
ได้ ผ่าน UIHostingController (SwiftUI ใน UIKit) และ UIViewRepresentable (UIKit ใน SwiftUI) เป็นแนวทางแบบผสมผสานที่นิยมระหว่างการย้ายข้อมูล
iOS 17 ให้ฟังก์ชันการทำงานเต็มรูปแบบ: NavigationStack, Observation framework, Swift Charts iOS 15 เป็นเกณฑ์ขั้นต่ำสำหรับการผลิต
สาเหตุที่พบบ่อยที่สุดคือการเปลี่ยนคุณสมบัติ @Published บนเธรดพื้นหลัง ObservableObject ต้องส่งการเปลี่ยนแปลงบน main actor: @MainActor class ViewModel
ใช้ .debounce ผ่าน Combine: Button.publisher(for: .tap) .debounce(for: .seconds(0.3), scheduler: RunLoop.main)
ใช่ ผ่านตัวปรับแต่ง Gesture: DragGesture, LongPressGesture, MagnificationGesture, RotationGesture รวมกันโดยใช้ .simultaneousGesture() และ .sequenced()
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ