WWDC 2019์ State of the Union ์ธ์
์ ์ฐธ์ํ ํ ์ ๋ SwiftUI์ ๋ํด ์์ธํ ์์๋ณด๊ธฐ๋ก ๊ฒฐ์ ํ์ต๋๋ค. ์ ๋ ์ด ์์
์ ๋ง์ ์๊ฐ์ ๋ณด๋๊ณ ์ด์ ๊ด๋ฒ์ํ ์ฌ์ฉ์์๊ฒ ์ ์ฉํ ์ ์๋ ์ค์ ์์ฉ ํ๋ก๊ทธ๋จ์ ๊ฐ๋ฐํ๊ธฐ ์์ํ์ต๋๋ค.
์ ๋ ์ด๊ฒ์ MovieSwiftUI๋ผ๊ณ ๋ถ๋ ์ต๋๋ค. ์ด๊ฒ์ ์ ์ํ์ ์ค๋๋ ์ํ๋ฅผ ๊ฒ์ํ๊ณ ๋ค์์ ์ฌ์ฉํ์ฌ ์ปฌ๋ ์
์ผ๋ก ์์งํ๋ ์ฑ์
๋๋ค.
์๋ฆผ: "Habr"์ ๋ชจ๋ ๋ ์๋ฅผ ์ํ - "Habr" ํ๋ก๋ชจ์ ์ฝ๋๋ฅผ ์ฌ์ฉํ์ฌ Skillbox ๊ณผ์ ์ ๋ฑ๋กํ ๋ 10 ๋ฃจ๋ธ ํ ์ธ.
Skillbox๋ ๋ค์์ ๊ถ์ฅํฉ๋๋ค. ๊ต์ก์ฉ ์จ๋ผ์ธ ๊ณผ์
"์ง์ ์๋ฐ ๊ฐ๋ฐ์" .
๊ทธ๋ ๋ค๋ฉด MovieSwiftUI๋ ๋ฌด์์ ํ ์ ์๋์?
- API์ ์ํธ์์ฉํฉ๋๋ค. ๊ฑฐ์ ๋ชจ๋ ์ต์ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ด๋ฅผ ์ํํฉ๋๋ค.
- ์์ฒญ ์ ๋น๋๊ธฐ ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํ๊ณ ๋ค์์ ์ฌ์ฉํ์ฌ JSON์ Swift ๋ชจ๋ธ๋ก ๊ตฌ๋ฌธ ๋ถ์ํฉ๋๋ค.
์ฝ๋ฉ ๊ฐ๋ฅ . - ์์ฒญ ์ ๋ก๋๋ ์ด๋ฏธ์ง๋ฅผ ํ์ํ๊ณ ์บ์ํฉ๋๋ค.
- iOS, iPadOS, macOS์ฉ ์ด ์ฑ์ ์ด๋ฌํ OS ์ฌ์ฉ์์๊ฒ ์ต๊ณ ์ UX๋ฅผ ์ ๊ณตํฉ๋๋ค.
- ์ฌ์ฉ์๋ ๋ฐ์ดํฐ๋ฅผ ์์ฑํ๊ณ ์์ ๋ง์ ์ํ ๋ชฉ๋ก์ ๋ง๋ค ์ ์์ต๋๋ค. ์์ฉ ํ๋ก๊ทธ๋จ์ ์ฌ์ฉ์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ณ ๋ณต์ํฉ๋๋ค.
- ๋ทฐ, ๊ตฌ์ฑ ์์ ๋ฐ ๋ชจ๋ธ์ Redux ํจํด์ ์ฌ์ฉํ์ฌ ๋ช ํํ๊ฒ ๊ตฌ๋ถ๋ฉ๋๋ค. ์ฌ๊ธฐ์ ๋ฐ์ดํฐ ํ๋ฆ์ ๋จ๋ฐฉํฅ์ ๋๋ค. ์์ ํ ์บ์, ๋ณต์ ๋ฐ ๋ฎ์ด์ธ ์ ์์ต๋๋ค.
- ์ด ์ ํ๋ฆฌ์ผ์ด์ ์ SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal ๋ฑ์ ๊ธฐ๋ณธ ๊ตฌ์ฑ ์์๋ฅผ ์ฌ์ฉํฉ๋๋ค. ๋ํ ์ฌ์ฉ์ ์ ์ ๋ณด๊ธฐ, ์ ์ค์ฒ, UI/UX๋ฅผ ์ ๊ณตํฉ๋๋ค.
์ฌ์ค ์ ๋๋ฉ์ด์
์ ๋งค๋๋ฌ์ ๊ณ , GIF๋ ์ข ์๋ฑํ๊ฒ ๋์๋ค์.
์ฑ ์์ ์ ํ๋ฉด์ ๋ง์ ๊ฒฝํ์ ํ๊ฒ ๋์๊ณ ์ ๋ฐ์ ์ผ๋ก ๊ธ์ ์ ์ธ ๊ฒฝํ์ ํ์ต๋๋ค. ์ ๋ ์์ ํ ๊ธฐ๋ฅ์ ๊ฐ์ถ ์ ํ๋ฆฌ์ผ์ด์ ์ ์์ฑํ ์ ์์๊ณ , 13์์ ์ด๋ฅผ ๊ฐ์ ํ์ฌ iOS XNUMX ์ถ์์ ๋์์ AppStore์ ๊ฒ์ํ ์์ ์ ๋๋ค.
Redux, BindableObject ๋ฐ EnvironmentObject
๋๋ ์ฝ XNUMX๋
๋์ Redux๋ฅผ ์ฌ์ฉํด์๊ธฐ ๋๋ฌธ์ ์๋์ ์ผ๋ก Redux์ ๋ํด ์ ์๊ณ ์์ต๋๋ค. ํนํ ์ ๋ ํ๋ก ํธ์๋์์ ์ด๊ฒ์ ์ฌ์ฉํฉ๋๋ค.
์ ๋ SwiftUI ์ ํ๋ฆฌ์ผ์ด์ ๊ตฌ์ถ์ ์ํ ๋ฐ์ดํฐ ํ๋ฆ ์ํคํ ์ฒ๋ก Redux๋ฅผ ์ ํํ ๊ฒ์ ๊ฒฐ์ฝ ํํํ์ง ์์ต๋๋ค. UIKit ์ฑ์์ Redux๋ฅผ ์ฌ์ฉํ ๋ ๊ฐ์ฅ ์ด๋ ค์ด ๋ถ๋ถ์ ์คํ ์ด์ ์์ ํ๊ณ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ ๊ฒ์ํ์ฌ ๋ทฐ/์ปดํฌ๋ํธ์ ๋งคํํ๋ ๊ฒ์ ๋๋ค. ์ด๋ฅผ ์ํด ReSwift ๋ฐ ReKotlin์ ์ฌ์ฉํ์ฌ ์ผ์ข ์ ์ปค๋ฅํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ง๋ค์ด์ผ ํ์ต๋๋ค. ์ ์๋ํ์ง๋ง ์ฝ๋๊ฐ ๊ฝค ๋ง์ต๋๋ค. ๋ถํํ๋ ์์ง์ ์คํ ์์ค๊ฐ ์๋๋๋ค.
์ข์ ์์! Redux๋ฅผ ์ฌ์ฉํ ๊ณํ์ด๋ผ๋ฉด SwiftUI์์ ๊ฑฑ์ ํด์ผ ํ ์ ์ผํ ๊ฒ์ ์์ , ์ํ ๋ฐ ๋ฆฌ๋์์ ๋๋ค. @EnvironmentObject ๋๋ถ์ SwiftUI๊ฐ ์์ ๊ณผ์ ์ํธ์์ฉ์ ์๋ฒฝํ๊ฒ ์ฒ๋ฆฌํฉ๋๋ค. ๋ฐ๋ผ์ ์ ์ฅ์๋ BindableObject๋ก ์์๋ฉ๋๋ค.
๊ฐ๋จํ Swift ํจํค์ง๋ฅผ ๋ง๋ค์์ต๋๋ค.
์ด๋ป๊ฒ ์๋ํฉ๋๊น?
final public class Store<State: FluxState>: BindableObject {
public let willChange = PassthroughSubject<Void, Never>()
private(set) public var state: State
private func _dispatch(action: Action) {
willChange.send()
state = reducer(state, action)
}
}
์์ ์ ์คํํ ๋๋ง๋ค ๊ธฐ์ด๋ฐ์ค๊ฐ ํ์ฑํ๋ฉ๋๋ค. ์ ํ๋ฆฌ์ผ์ด์ ์ ํ์ฌ ์ํ์ ๋ฐ๋ผ ์์ ์ ํ๊ฐํฉ๋๋ค. ๊ทธ๋ฐ ๋ค์ ์์ ์ ํ ๋ฐ ๋ฐ์ดํฐ์ ๋ฐ๋ผ ์์ ๋ ์ ์ํ๋ฅผ ๋ฐํํฉ๋๋ค.
store๋ BindableObject์ด๊ธฐ ๋๋ฌธ์ PassthroughSubject์์ ์ ๊ณตํ๋ willChange ์์ฑ์ ์ฌ์ฉํ์ฌ ๊ฐ์ด ๋ณ๊ฒฝ๋๋ฉด SwiftUI์ ์๋ฆฝ๋๋ค. ์ด๋ BindableObject๊ฐ PublisherType์ ์ ๊ณตํด์ผ ํ์ง๋ง ํ๋กํ ์ฝ ๊ตฌํ์ด ์ด๋ฅผ ๊ด๋ฆฌํ ์ฑ ์์ด ์๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ ๋ฐ์ ์ผ๋ก ์ด๊ฒ์ Apple์ ๋งค์ฐ ๊ฐ๋ ฅํ ๋๊ตฌ์ ๋๋ค. ๋ฐ๋ผ์ ๋ค์ ๋ ๋๋ง ์ฃผ๊ธฐ์์ SwiftUI๋ ์ํ ๋ณ๊ฒฝ์ ๋ฐ๋ผ ๋ทฐ์ ๋ณธ๋ฌธ์ ๋ ๋๋งํ๋ ๋ฐ ๋์์ ์ค๋๋ค.
์ค์ ๋ก ์ด๊ฒ์ด SwiftUI์ ํต์ฌ์ด์ ๋ง๋ฒ์ ๋๋ค. ์ด์ ์ํ๋ฅผ ๊ตฌ๋ ํ๋ ๋ชจ๋ ๋ทฐ์์๋ ์ํ์์ ์์ ๋ ๋ฐ์ดํฐ์ ๋ณ๊ฒฝ๋ ๋ด์ฉ์ ๋ฐ๋ผ ๋ทฐ๊ฐ ๋ ๋๋ง๋ฉ๋๋ค.
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
let controller = UIHostingController(rootView: HomeView().environmentObject(store))
window.rootViewController = controller
self.window = window
window.makeKeyAndVisible()
}
}
}
struct CustomListCoverRow : View {
@EnvironmentObject var store: Store<AppState>
let movieId: Int
var movie: Movie! {
return store.state.moviesState.movies[movieId]
}
var body: some View {
HStack(alignment: .center, spacing: 0) {
Image(movie.poster)
}.listRowInsets(EdgeInsets())
}
}
Store๋ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์์๋ ๋ EnvironmentObject๋ก ์ฃผ์ ๋๊ณ @EnvironmentObject๋ฅผ ์ฌ์ฉํ์ฌ ๋ชจ๋ ๋ทฐ์์ ์ก์ธ์คํ ์ ์์ต๋๋ค. ํ์ ์์ฑ์ ์ ํ๋ฆฌ์ผ์ด์ ์ํ์์ ๋น ๋ฅด๊ฒ ๊ฒ์๋๊ฑฐ๋ ๊ณ์ฐ๋๋ฏ๋ก ์ฑ๋ฅ ์ ํ๊ฐ ์์ต๋๋ค.
์์ ์ฝ๋๋ ์ํ ํฌ์คํฐ๊ฐ ๋ณ๊ฒฝ๋๋ฉด ์ด๋ฏธ์ง๋ฅผ ๋ณ๊ฒฝํฉ๋๋ค.
๊ทธ๋ฆฌ๊ณ ์ด๊ฒ์ ์ค์ ๋ก ๋จ ํ๋์ ์ ์ผ๋ก ์ด๋ฃจ์ด์ง๋ฉฐ, ์ด๋ฅผ ํตํด ๋ทฐ๊ฐ ์ํ์ ์ฐ๊ฒฐ๋ฉ๋๋ค. iOS์์ ReSwift๋ฅผ ์ฌ์ฉํด๋ณธ ์ ์ด ์๊ฑฐ๋ ์ฌ์ง์ด
์ด์ ์์ ์ ํ์ฑํํ๊ณ ์ ์ํ๋ฅผ ๊ฒ์ํ ์ ์์ต๋๋ค. ๋ค์์ ๋ ๋ณต์กํ ์์ ๋๋ค.
struct CustomListDetail : View {
@EnvironmentObject var store: Store<AppState>
let listId: Int
var list: CustomList {
store.state.moviesState.customLists[listId]!
}
var movies: [Int] {
list.movies.sortedMoviesIds(by: .byReleaseDate, state: store.state)
}
var body: some View {
List {
ForEach(movies) { movie in
NavigationLink(destination: MovieDetail(movieId: movie).environmentObject(self.store)) {
MovieRow(movieId: movie, displayListImage: false)
}
}.onDelete { (index) in
self.store.dispatch(action: MoviesActions.RemoveMovieFromCustomList(list: self.listId, movie: self.movies[index.first!]))
}
}
}
}
์ ์ฝ๋์์๋ ๊ฐ IP์ ๋ํด SwiftUI์ .onDelete ์์ ์ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ์ด๋ฅผ ํตํด ๋ชฉ๋ก์ ํ์ ์ผ๋ฐ iOS ์ค์์ดํ๋ฅผ ํ์ํ์ฌ ์ญ์ ํ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ ์ฌ์ฉ์๊ฐ ์ญ์ ๋ฒํผ์ ํฐ์นํ๋ฉด ํด๋น ์์ ์ด ์คํ๋๊ณ ๋ชฉ๋ก์์ ์ํ๊ฐ ์ ๊ฑฐ๋ฉ๋๋ค.
์, ๋ชฉ๋ก ์์ฑ์ BindableObject ์ํ์์ ํ์๋๊ณ EnvironmentObject๋ก ์ฃผ์ ๋๋ฏ๋ก ForEach๊ฐ ์ํ ๊ณ์ฐ ์์ฑ๊ณผ ์ฐ๊ฒฐ๋์ด ์๊ธฐ ๋๋ฌธ์ SwiftUI๋ ๋ชฉ๋ก์ ์ ๋ฐ์ดํธํฉ๋๋ค.
๋ค์์ MoviesState ๊ฐ์๊ธฐ์ ์ผ๋ถ์ ๋๋ค.
func moviesStateReducer(state: MoviesState, action: Action) -> MoviesState {
var state = state
switch action {
// other actions.
case let action as MoviesActions.AddMovieToCustomList:
state.customLists[action.list]?.movies.append(action.movie)
case let action as MoviesActions.RemoveMovieFromCustomList:
state.customLists[action.list]?.movies.removeAll{ $0 == action.movie }
default:
break
}
return state
}
์์์ ์ค๋ช ํ ๋๋ก ์ก์ ์ ์ ๋ฌํ๊ณ ์ ์ํ๋ฅผ ๋ฐํํ๋ฉด ๋ฆฌ๋์๊ฐ ์คํ๋ฉ๋๋ค.
์์ง์ ์์ธํ ์ค๋ช
ํ์ง ์๊ฒ ์ต๋๋ค - ์ด๋ป๊ฒ SwiftUI๊ฐ ํ์ํ ๋ด์ฉ์ ์ค์ ๋ก ์๋์ง. ์ด๊ฒ์ ๋ ๊น์ด ์ดํดํ๋ ค๋ฉด ๊ฐ์น๊ฐ ์์ต๋๋ค.
Skillbox๋ ๋ค์์ ๊ถ์ฅํฉ๋๋ค.
- ์ค๊ธฐ ์ฝ์ค
"๋ชจ๋ฐ์ผ ๊ฐ๋ฐ์ PRO" .- ์จ๋ผ์ธ ๊ฐ์ข ์ ์ฉ
"ํ์ด์ฌ ๋ฐ์ดํฐ ๋ถ์๊ฐ" .- XNUMX๋ ์ค์ต ์ฝ์ค
"์ ๋ PRO ์น ๊ฐ๋ฐ์์ ๋๋ค" .
์ถ์ฒ : habr.com