SwiftUI — แนวคิดหลัก: View, State และ Data Flow

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-02-21 เวลาอ่าน: 8 นาที

SwiftUI — เฟรมเวิร์กแบบประกาศของ Apple สำหรับสร้างส่วนติดต่อผู้ใช้บนทุกแพลตฟอร์มในระบบนิเวศ เปิดตัวที่ WWDC 2019 แตกต่างจาก UIKit เชิงคำสั่งที่มี viewDidLoad และการอัปเดตหน้าจอด้วยตนเอง SwiftUI อธิบาย UI เป็นชุดของโครงสร้างง่าย ๆ ที่สอดคล้องกับโปรโตคอล View ตามข้อมูลจาก Swift.org (2025) พบว่า SwiftUI ถูกใช้ใน 65% ของโปรเจกต์ใหม่ที่เผยแพร่บน App Store เฟรมเวิร์กจัดการอัปเดตอินเทอร์เฟซโดยอัตโนมัติผ่านกลไก State และ Data Flow — เมื่อข้อมูลเปลี่ยนแปลง View จะถูกวาดใหม่โดยไม่ต้องเรียก reloadData ด้วยตนเอง

ประเด็นสำคัญ

  • SwiftUI — เฟรมเวิร์ก UI แบบประกาศของ Apple (2019) ซึ่งอินเทอร์เฟซถูกอธิบายโดยโครงสร้างที่สอดคล้องกับโปรโตคอล View
  • View — บล็อกพื้นฐานของ SwiftUI; แต่ละ View อธิบายส่วนของหน้าจอผ่าน computed property body
  • @State — property wrapper สำหรับเก็บสถานะท้องถิ่น เมื่อเปลี่ยนแล้ว View จะถูกวาดใหม่โดยอัตโนมัติ
  • @Binding — การเชื่อมต่อสองทางระหว่าง View และข้อมูล ทำให้ View ลูกสามารถเปลี่ยนสถานะของพ่อแม่ได้
  • @ObservedObject และ @StateObject — การเชื่อมต่อกับโมเดลข้อมูลภายนอกผ่านคลาสที่สอดคล้องกับโปรโตคอล ObservableObject

SwiftUI คืออะไร?

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

SwiftUI เทียบกับ UIKit

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

ใน 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

View — โปรโตคอลที่มีข้อกำหนดเดียว: computed property body ชนิด some View แต่ละโครงสร้างที่สอดคล้องกับ View จะอธิบายส่วนของหน้าจอใน body ชนิด some View เป็นชนิดส่งกลับแบบทึบแสงที่ซ่อนชนิดรูปธรรมของ View ที่ถูกส่งกลับ (การซ้อน VStack, HStack, ZStack, Text, Image ฯลฯ) คอมไพเลอร์ของ Swift อนุมานชนิดรูปธรรมในเวลาคอมไพล์ รักษาประสิทธิภาพของการเรียกโดยตรงโดยไม่ต้องลบชนิด

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: สถานะท้องถิ่นใน SwiftUI

@State — property wrapper ที่ประกาศสถานะท้องถิ่นที่เป็นของ View เดียว SwiftUI จัดการหน่วยความจำ State โดยอัตโนมัติ: เมื่อค่าเปลี่ยน body จะถูกวาดใหม่ แต่เฉพาะสำหรับ Views ที่ใช้ State นั้น State เป็น source of truth สำหรับชนิดง่าย ๆ (String, Int, Bool, enum) อย่าใช้ @State สำหรับโมเดลข้อมูลที่ซับซ้อน — ให้ใช้ @StateObject และ @ObservedObject แทน State ควรเป็น private และถูกเก็บไว้ภายใน View เอง ไม่ควรส่งต่อระหว่างคอมโพเนนต์

swift
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: การสื่อสารสองทางระหว่าง Views

@Binding — property wrapper ที่สร้างการเชื่อมต่อสองทางระหว่าง View และข้อมูลที่ View ไม่ได้เป็นเจ้าของ Binding เป็นการอ้างอิงถึง State (หรือ source of truth อื่น) ที่ช่วยให้ View ลูกสามารถอ่านและเปลี่ยนค่าที่เก็บไว้ในพ่อแม่ได้ Binding แสดงด้วยคำนำหน้า $: $count ส่ง Binding<Int> ไปยัง View ลูก หากไม่มี Binding View ลูกจะไม่สามารถเปลี่ยนข้อมูลของพ่อแม่ได้ — สามารถอ่านได้เท่านั้น

swift
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 สำหรับคอนโทรลแบบกำหนดเอง ตัวแก้ไข และคอมโพเนนต์ที่ใช้ซ้ำได้ซึ่งต้องเปลี่ยนข้อมูลของพ่อแม่

@ObservedObject และ @StateObject: โมเดลข้อมูลภายนอก

@StateObject — property wrapper สำหรับสร้างและเป็นเจ้าของอินสแตนซ์ของคลาสที่สอดคล้องกับ ObservableObject View สร้างวัตถุหนึ่งครั้งต่อวงจรชีวิตและถูกวาดใหม่เมื่อคุณสมบัติ @Published ของมันเปลี่ยนแปลง @ObservedObject — wrapper ที่คล้ายกัน แต่ View ไม่ได้เป็นเจ้าของวัตถุ — วัตถุถูกสร้างและเก็บไว้นอก View (ส่งผ่านตัวเริ่มต้น) Apple แนะนำ @StateObject สำหรับ source of truth ในลำดับชั้นของ View และ @ObservedObject สำหรับการฉีดการพึ่งพา

swift
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:

Data Flow ใน SwiftUI: ภาพรวมทั้งหมด

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ภายนอกการอ้างอิงถึง StateView ลูกเปลี่ยนข้อมูลของพ่อแม่
@StateObjectView เป็นเจ้าของReference (class)Source of truth สำหรับโมเดลข้อมูลที่ซับซ้อน
@ObservedObjectการฉีดReference (class)โมเดลที่สร้างภายนอก View (ส่งผ่าน init)
@EnvironmentObjectทั่วโลกReference (class)ข้อมูลที่พร้อมใช้งานสำหรับลำดับชั้นทั้งหมด (auth, theme)

คำถามที่พบบ่อย

@State แตกต่างจาก @StateObject อย่างไร?

@State — สำหรับ value types (struct, enum, String, Int) และสถานะท้องถิ่นของ View เดียว SwiftUI จัดการหน่วยความจำ State โดยอัตโนมัติ @StateObject — สำหรับ reference types (class) ที่สอดคล้องกับ ObservableObject @StateObject เป็นเจ้าของวัตถุและวาด View ใหม่เมื่อคุณสมบัติ @Published เปลี่ยนแปลง สำหรับตัวนับธรรมดาให้ใช้ @State; สำหรับโมเดลที่มีตรรกะทางธุรกิจให้ใช้ @StateObject

สามารถใช้ SwiftUI กับ UIKit ได้หรือไม่?

ได้ SwiftUI ผสานรวมกับ UIKit ผ่าน UIHostingController (SwiftUI ภายใน UIKit) และ UIViewRepresentable (UIKit ภายใน SwiftUI) UIHostingController ห่อ SwiftUI View ใน UIViewController UIViewRepresentable ช่วยให้ใช้คอมโพเนนต์ UIKit (MKMapView, WKWebView) ใน SwiftUI นี่เป็นวิธีมาตรฐานสำหรับการโยกย้ายโปรเจกต์จาก UIKit ไปยัง SwiftUI

ViewBuilder ใน SwiftUI คืออะไร?

ViewBuilder — result builder (Swift 5.1) ที่แปลงชุดของ Views เป็นค่าเดียวชนิด TupleView, Group หรือ ConditionalContent ViewBuilder ช่วยให้เขียน if/else และ switch เชิงคำสั่งภายใน body แบบประกาศ หากไม่มี ViewBuilder คุณจะต้องส่งคืน AnyView หรือ Group สำหรับแต่ละบล็อกเงื่อนไข ViewBuilder เป็นเหตุผลที่ body ไม่ต้องใช้เครื่องหมายจุลภาคระหว่าง Views

SwiftUI ทำงานบนอุปกรณ์ Apple ทั้งหมดหรือไม่?

ได้ SwiftUI รองรับ iOS 13+, iPadOS 13+, macOS 10.15+, watchOS 6+, tvOS 13+ และ visionOS 1+ อย่างไรก็ตาม API บางอย่างใช้ได้เฉพาะในเวอร์ชันใหม่กว่าเท่านั้น: ตัวอย่างเช่น navigationStack (iOS 16+), Observable macro (iOS 17+) สำหรับความเข้ากันได้ย้อนหลัง ให้ใช้ #available และการปรับเปลี่ยนของ UIKit

จะดีบักแอปพลิเคชัน SwiftUI ได้อย่างไร?

Xcode Debug View Hierarchy แสดงทรีของ SwiftUI View พร้อมตัวปรับแต่งและเฟรม เครื่องมือ SwiftUI Inspector (แผงด้านขวาของ Xcode) ช่วยให้ปรับเปลี่ยนตัวปรับแต่งแบบเรียลไทม์ self._printChanges() ใน body จะบันทึกสาเหตุของการวาดใหม่ Instruments กับเทมเพลต SwiftUI จะติดตามประสิทธิภาพของ View และระบุการวาดใหม่ที่มากเกินไป

สรุป

  • SwiftUI — เฟรมเวิร์ก UI แบบประกาศของ Apple ซึ่งอินเทอร์เฟซถูกอธิบายโดยโครงสร้าง View กับ computed property body (2019)
  • View — value type (struct) ที่สอดคล้องกับโปรโตคอล View; body ส่งคืน some View ผ่าน ViewBuilder
  • @State — สถานะท้องถิ่นสำหรับ value types; เมื่อเปลี่ยน View จะถูกวาดใหม่โดยอัตโนมัติ
  • @Binding — การเชื่อมต่อสองทางผ่านคำนำหน้า $; View ลูกเปลี่ยนข้อมูลของพ่อแม่
  • @StateObject / @ObservedObject — reference types กับ ObservableObject และคุณสมบัติ @Published; StateObject เป็นเจ้าของวัตถุ, ObservedObject รับจากภายนอก
  • @EnvironmentObject — สถานะทั่วโลกสำหรับลำดับชั้น View ทั้งหมด; ถูกฉีดผ่าน .environmentObject()
  • Data Flow ใน SwiftUI — จาก State (ท้องถิ่น) ผ่าน Binding (สองทาง) ไปยัง ObservedObject (โมดูล) และ EnvironmentObject (ทั่วโลก)

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม