Post

2️⃣ WWDC 2025 -SwiftUI 7.0- iOS 26

✅ FoundationModels (SDK)

(온디바이스 인텔리전스)

Xcode 26에는 이제 FoundationModels SDK가 포함되어, 온디바이스 인텔리전스 모델을 활용할 수 있습니다. 이 모델들은 디바이스 내에서 동작하기 때문에, 디바이스가 오프라인 상태여도 기능을 수행할 수 있습니다. 이제 이 새로운 SDK의 핵심적인 활용 사례들을 살펴보겠습니다.

⭐️ iPhone 15 이상 또는 M1 칩이 달린 iPad / Mac 이후 기종 만 지원됨

+. streamResponse 코드 몇 줄만으로도 우리는 이 새로운 SDK를 이용해 AI 채팅을 만들 수 있습니다. 하지만 답변이 너무 오래 걸린다는 것을 눈치채셨나요?

네, 그 이유는 제가 한 번에 전체 결과를 요청하고 있기 때문입니다. 하지만 부분적인 반복 결과(Partial Iterative Results)를 받을 수 있는 방법이 있습니다. 이 방식은 요즘 AI 채팅에서 가장 흔히 사용되고 있죠.

0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import FoundationModels

struct ContentView: View {
    @State private var prompt: String = ""
    @State private var answer: String = ""
    @State private var disableControls: Bool = false
    var body: some View {
        NavigationStack {
            ScrollView(.vertical) {
                Text(answer)
                    .frame(maxWidth: .infinity, alignment: .leading)
                    .multilineTextAlignment(.leading)
                    .padding(15)
            }
            .safeAreaBar(edge: .bottom) {
                HStack(spacing: 10) {
                    TextField("Prompt", text: $prompt)
                        .padding(.horizontal, 15)
                        .padding(.vertical, 10)
                        .glassEffect(.regular, in: .capsule)
                    
                    Button {
                        Task {
                            guard !prompt.isEmpty else { return }
                            
                            do {
                                let session = LanguageModelSession()
                                disableControls = true
                                
                                let response = session.streamResponse(to: prompt)
                                
                                
                                for try await chunk in response {
                                    self.answer = chunk.content
                                }
                                
                                disableControls = false
                            }catch {
                                disableControls = false
                                print(error.localizedDescription)
                            }
                        }
                        
                    } label: {
                        Image(systemName: "paperplane.fill")
                            .frame(width: 30, height: 30)
                    }
                    .buttonStyle(.glass)
                }
                .disabled(disableControls)
                .padding(25)
                
            }
            .navigationTitle("Foundataion Model")
        }
    }
}

✅  FoundationModels (@Generable)

FoundationModels에는 @Generable 이라는 매크로가 포함되어 있으며, 이를 통해 LanguageModelSession이 지정된 모델을 위한 데이터를 생성할 수 있습니다.

이 예시에서는 Todo 라는 이름의 모델을 생성할 것입니다.

이 매크로를 사용하면, 언어 모델에게 무작위 할 일(todo) 목록을 생성하도록 요청할 수 있습니다.

비록 이것이 실질적인 사용 사례는 아닐 수 있지만, 다른 많은 시나리오에서 유용하게 활용될 수 있습니다!

+. @Guide()

이것은 LanguageModel에 이러한 속성을 채우는 데 필요한 컨텍스트를 제공합니다. 또한, @Generable에도 이러한 설명을 제공할 수 있습니다!

@Guide() 적용을 하면 아래 사진 처럼 Contenxt 가 @Guide(description: “칫솔질”)에 따라 비슷한 형식으로 변경된다.

1

2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import SwiftUI
import FoundationModels

struct ContentView: View {
    @State private var todos: [Todo] = []
    @State private var isWriting: Bool = false
    var body: some View {
        NavigationStack {
            List {
                ForEach(todos) { todo in
                    Text(todo.task)
                }
            }
            .navigationTitle("Todo")
            .toolbar(content: {
                ToolbarItem(placement: .topBarTrailing) {
                    Button("", systemImage: "apple.intelligence") {
                        let propmt = "Create 10 todo list items in Korean"
                        Task {
                            do {
                                let session = LanguageModelSession()
                                let response = session.streamResponse(generating: [Todo].self) {
                                    propmt
                                }
                                
                                isWriting = true
                                for try await chunkTodos in response {
                                    self.todos = chunkTodos.content.compactMap({
                                        if let id = $0.id, let task = $0.task {
                                            return .init(id: id, task: task)
                                        }
                                        
                                        return nil
                                    })
                                }
                                
                                isWriting = false
                            }catch {
                                isWriting = false
                                print(error.localizedDescription)
                            }
                        }
                    }
                    .disabled(isWriting)
                }
            })
        }
    }
}

@Generable
struct Todo: Identifiable {
    var id: String
    @Guide(description: "칫솔질") //어떤 내용이 올지 샘플을 제공함
    var task: String
}

✅ scrollEdgeEffectStyle()

기본적으로, List, Navigation, 그리고 다른 UI 컴포넌트들은 이제 안전 영역(safe areas)에 부드러운 블러 효과(Progressive Blurs라고도 불림)를 갖습니다. 그러나 SwiftUI는 이러한 효과를 제어할 수 있는 간단한 수정자(modifier)를 제공합니다.

3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import SwiftUI
import FoundationModels

struct ContentView: View {
    @State private var todos: [Todo] = []
    @State private var isWriting: Bool = false
    var body: some View {
        NavigationStack {
            List {
                ForEach(todos) { todo in
                    Text(todo.task)
                }
            }
            .navigationTitle("Todo")
            .toolbar(content: {
                ToolbarItem(placement: .topBarTrailing) {
                    Button("", systemImage: "apple.intelligence") {
                        let propmt = "Create 10 todo list items in Korean"
                        Task {
                            do {
                                let session = LanguageModelSession()
                                let response = session.streamResponse(generating: [Todo].self) {
                                    propmt
                                }
                                
                                isWriting = true
                                for try await chunkTodos in response {
                                    self.todos = chunkTodos.content.compactMap({
                                        if let id = $0.id, let task = $0.task {
                                            return .init(id: id, task: task)
                                        }
                                        
                                        return nil
                                    })
                                }
                                
                                isWriting = false
                            }catch {
                                isWriting = false
                                print(error.localizedDescription)
                            }
                        }
                    }
                    .disabled(isWriting)
                }
            })
            .scrollEdgeEffectStyle(.hard, for: .top)
        }
    }
}

backgroundExtensionEffect()

작은 이미지를 사용할 때 잠금 화면 상단에서 확장된 블러 효과가 나타나는 것을 모두 본 적이 있을 것입니다.

그런데 이제 이 기능이 SwiftUI에서 modifier(수정자) 로 제공됩니다!

이 수정자는 뷰를 사용 가능한 안전 영역(safe areas)까지 확장하며, 그 영역에 은은한 블러 효과를 적용합니다.

4

5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import SwiftUI

struct ContentView: View {
    var body: some View {
        GeometryReader {
            let size = $0.size
            Image(.pic2)
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: size.width, height: size.height)
                .clipped()
                .backgroundExtensionEffect()
        }
    }
}

✅ Rich TextEditor

마침내, TextEditor가 이제 AttributedString 을 Binding으로 지원합니다. 또한 기본 제공 TextEditor에는 몇 가지 유용한 리치 텍스트 편집 기능 옵션도 포함되어 있습니다!

6

7

1
2
3
4
5
6
7
8
9
10
import SwiftUI

struct ContentView: View {
    @State private var richText = AttributedString()
    var body: some View {
        TextEditor(text: $richText)
            .frame(height: 300)
            .padding(15)
    }
}

✅ Native WebView

SwiftUI가 이제 Native WebView 를 지원합니다. 스크롤 위치 추적, 스크롤 위치 업데이트, 특정 제스처 비활성화 등 다양한 기본 내장 기능들이 포함되어 있습니다.

8

1
2
3
4
5
6
7
8
9
10
11
12
import SwiftUI
import WebKit

struct ContentView: View {
    var body: some View {
        WebView(url: url)
    }
    
    var url: URL {
        URL(string: "https://developer.apple.com")!
    }
}

WebPage()

WebPage를 사용하여 페이지를 불러올 수도 있습니다. 이렇게 하면 웹 페이지를 보다 프로그래밍적으로 제어할 수 있습니다!

9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import SwiftUI
import WebKit

struct ContentView: View {
    @State private var page = WebPage()
    var body: some View {
        WebView(page)
            .webViewMagnificationGestures(.disabled)
            .onAppear {
                page.load(URLRequest(url: url))
            }
    }
    
    var url: URL {
        URL(string: "https://developer.apple.com")!
    }
}

TabView Customizations

기본적으로, SwiftUI는 새로운 iOS 26 글로시 탭 바(glossy tab bar) 에 자동으로 적용됩니다. (단, 앱이 이전에 네이티브 탭 바를 사용했을 경우이며, 커스텀 탭 바를 사용한 경우는 제외됩니다.)

iOS 26에서는 검색(Search) 역할의 탭 아이템이 탭 바에서 제외되어, 탭 바와는 별도로 표시됩니다!

10

11

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import SwiftUI

struct ContentView: View {
    var body: some View {
        TabView {
            Tab.init("Home", systemImage: "house.fill") {
                Text("Home")
            }
            
            Tab.init("Favorite", systemImage: "suit.heart.fill") {
                Text("Favorite")
            }
            
            Tab.init("Settings", systemImage: "gearshape.fill") {
                Text("Settings")
            }
            
            Tab.init("Search", systemImage: "magnifyingglass", role: .search) {
                Text("Search")
            }
        }
    }
}

+. tabViewBottomAccessory()

iOS 26에서는 TabView 가 이제 탭 바 위에 액세서리 뷰를 추가하는 것을 지원합니다.

(Apple Music 앱과 동일한 방식입니다.)

12

1
2
3
4
5
6
7
8
9
10
11
12
import SwiftUI

struct ContentView: View {
    var body: some View {
        TabView {...}
        .tabViewBottomAccessory {
            Text("Custom Music Player!")
                .padding(.horizontal, 15)
            
        }
    }
}

+. tabBarMinimizeBehaviour()

Apple Music 앱과 마찬가지로, 이제 탭 바는 스크롤을 내리거나(Scrolled Down) 올릴 때(Scrolled Up) 최소화(minimizing)를 지원합니다.

스크롤을 내리거나 올리면, 검색(Search) 역할의 탭 아이템은 오른쪽으로 이동하고, 탭 바는 최소화되어 왼쪽으로 밀립니다.

또한, 액세서리 뷰는 중앙으로 이동하게 됩니다.

13

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import SwiftUI

struct ContentView: View {
    var body: some View {
        TabView {
            Tab.init("Home", systemImage: "house.fill") {
                ScrollView(.vertical) {
                    Text("Home")
                        .containerRelativeFrame([.horizontal])
                        .containerRelativeFrame(.vertical) { value, _ in
                            value * 3
                        }
                }
            }
            
            Tab.init("Favorite", systemImage: "suit.heart.fill") {
                Text("Favorite")
            }
            
            Tab.init("Settings", systemImage: "gearshape.fill") {
                Text("Settings")
            }
            
            Tab.init("Search", systemImage: "magnifyingglass", role: .search) {
                Text("Search")
            }
        }
        .tabViewBottomAccessory {
            Text("Custom Music Player!")
                .padding(.horizontal, 15)
            
        }
        .tabBarMinimizeBehavior(.onScrollDown)
    }
}

📒 tabBarAccessoryPlacement 환경(environment)을 사용하여 탭 바 액세서리 뷰를 상황에 맞게 업데이트할 수 있습니다!

✅ ToolBarSpacer

기본적으로, 모든 ToolBar 아이템은 iOS 26에서 함께 그룹화됩니다. 그러나 경우에 따라 버튼을 그룹에서 분리해야 할 필요가 있을 수 있습니다. 이 새로운 modifier는 ToolBar 아이템을 분리하거나, 여러 개의 ToolBar 아이템 그룹을 만드는 데 특히 유용합니다.

14

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationStack {
            List {
                
            }
            .navigationTitle("Todo's")
            .toolbar {
                ToolbarItem(placement: .topBarTrailing) {
                    HStack {
                        Button("", systemImage: "suit.heart.fill") {
                            
                        }
                        Button("", systemImage: "magnifyingglass") {
                            
                        }
                    }
                }
                ToolbarSpacer(.fixed, placement: .topBarTrailing)
                
                ToolbarItem(placement: .topBarTrailing) {
                    Button("", systemImage: "person.fill") {
                        
                    }
                }
            }
        }
    }
}

소스정리

https://github.com/langpeu

출처

Kavsoft 영상을 공부하면서 정리한 내용

https://www.youtube.com/watch?v=jwN0o8xUdXk

This post is licensed under CC BY 4.0 by the author.