momodudu.zip
#1 SwiftUI - SwiftUI 시작해보기 Hello, World! 본문
오랜만에 쓰는 주말 포스팅!
이번에 새로운 프로젝트를 진행하면서 framework를 swiftUI를 쓰는 앱들에게 제공해주어야 할 경우가 생길것 같아서 SwiftUI에 대해서 새로 공부해보고자 한다! 한 이주전부터 주말에 해야지 해야지 하면서 열심히 놀고먹고 자느라 못했다 -_-...
일단 먼저, SwiftUI가 도대체 뭘까?
기존에 iOS 개발자들이 사용하던 UIKit는 Objective C 기반으로 만들어진 강력한 프레임워크였다. 그러나 오브젝티브씨는 사실 그 자체로도 매우 강력한 언어지만, 프로그래밍언어론적으로 현대적이지 않은 개념들이 여러가지 섞여있어서, 애플에서 Swift를 발표하게 된다. 그렇지만 UIKit은 오브젝티브씨 기반으로 만들어져있기 때문에, Swift가 빠르고 안정적이어도 @objc 키워드를 붙여주어야 한다든지... 스위프트랑 호환성을 만들기 위해서 여러가지 번거로운 작업들이 존재했다. 이런 번거로움을 해결하기위해서 혜성처럼 등장한것이 "Swift기반의 Swift Kit" , 즉 SwiftUI이다!
심지어 swiftUI는 별도의 프레임워크 분리 없이 AppKit, UIKit 이런식으로 분리해서 사용할 필요도 없이 모든 플랫폼에서 공통적으로 사용가능하다 :-)
몇가지 기능들을 만들어보면서, swiftUI가 어떻게 사용되는지 알아보려고 한다.
1. SwiftUI Project 생성
프로젝트 생성시 스토리보드가 아닌 스위프트UI를 선택하면 위와 같이 디폴트 프로젝트 구성이 완료된다!
스토리보드를 선택했을때와 다른점은, 기존에 존재하던 ViewController와 UI 구성을 할 수 있는 main.storyboard가 사라지고, ContentView라는 파일이 새로 생겼다. 또한 오른쪽에 프리뷰라는 화면이 새로 생겼다 :-)
이 프리뷰는 시뮬레이터를 동작시키지 않고도 실제로 작업중인 코드와 동기화되어서 결과물이 보이는 화면이라고 한다. 프리뷰에 작업한 결과물도 코드에 바로 동기화가 된다고 한다~
옵션+커맨드+P 클릭시 위와 같이 컨텐츠뷰의 코드가 프리뷰에 뜬다 +_+ 헬로우월드!
2. ContentView.swift 파일 코드 살펴보기
//
// ContentView.swift
// HelloSwiftUI
//
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello, World!")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
두개의 struct가 정의되어있는데, 두번째 구조체는 프리뷰를 위한 용도라서 일단은 넘어가고~ 이 프리뷰 구조체는 없어도 앱을 만들수는 있는 것 같다. 다만 엑스코드에서 프리뷰 기능을 활용하지 못할뿐~?
ContentView를 보면, 일단 먼저 View Protocol을 준수하는 구조체이다. 실제로 ViewController가 동작시키는 View는(엄밀히 말하면 UIKit의 UIView) 클래스형태이다. 그래서 사실 스위프트 문법상으로는 struct인 ContentView는 구조체이므로 class인 "UIView"를 상속받을 수 없다. 대신, 위에 컨텐츠뷰가 채택한것은 클래스가 아닌 View라는 이름의 프로토콜이다 :-)
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol View {
/// The type of view representing the body of this view.
///
/// When you create a custom view, Swift infers this type from your
/// implementation of the required `body` property.
associatedtype Body : View
/// Declares the content and behavior of this view.
var body: Self.Body { get }
}
실제로 정의를 따라가보면, 위와 같이 정의되어있다. iOS 13.0 부터 가능한가보다.. *_*
흥미로운점은, 이 뷰 프로토콜에서 필수로 구현해야 하는것으로 지정한것은 단지 body라는 이름의 computed property뿐이다. 오오..
그래서 위의 디폴트 프로젝트 예시에서도 컨텐츠뷰 구조체 안에 body computed property를 구현해주었다. 그런데 이 body 프로퍼티는 타입이 View 프로토콜을 준수하는 타입이어야 한다.. (?)
view protocol안에 유일하게 구현해야하는 프로퍼티가 view protocol을 준수하는 타입이어야되고, 그 view protocol안에는 또 view를 준수하는 body가... @.@ 즉, nested call이 발생하게 된다. 뭔말인가 싶지만, view protocol을 자세히 보고 차근차근 풀어쓰다보면 무슨말인지 대충 알 수 있다.
/// The type of view representing the body of this view.
///
/// When you create a custom view, Swift infers this type from your
/// implementation of the required `body` property.
associatedtype Body : View
/// Declares the content and behavior of this view.
var body: Self.Body { get }
위의 코드를 보면, body라는 연산 프로퍼티는 Body타입을 리턴하는 연산프로퍼티인데, 그래서 Body type이 뭐냐? protocol 내부 관련타입으로 Body로 선언되어있다. 즉, 프로토콜 내부 타입으로 Body는 사실 아무거나 넣을 수 있다. body를 Int나 String으로 리턴해도 되지만, 선언부에 보다시피 "View"라는 프로토콜 제약이 걸려있다. 즉! body 연산 프로퍼티는 View를 준수하는 Body타입은 뭐든지 다 넣을 수 있다는 말! 그렇다면 컨텐츠뷰 코드를 다시 보자.
struct ContentView: View {
var body: some View {
Text("Hello, World!")
}
}
우리의 body 연산프로퍼티는 View중 대~충 한 타입을 리턴할거고, 여기서는 Text를 리턴한다. 그렇다면 Text가 View를 준수하는 타입일까? 라는 생각이 든다. Text선언부를 따라가보면, extension으로 이것저것 많이 선언되어있다. 그중에서도!!!
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension Text : View {
/// The type of view representing the body of this view.
///
/// When you create a custom view, Swift infers this type from your
/// implementation of the required `body` property.
public typealias Body = Never
}
짠~. View프로토콜을 준수하는 Text Struct에서 typealias로 Body가 Never 타입으로 선언되어있음을 볼 수 있다. 아니 Never는 또 뭔데..
네버는 애플 도큐먼트에서 아래와 같이 설명되어있다.
Never
The return type of functions that do not return normally, that is, a type with no values.
응.. 아무것도 리턴할게 없을때 쓰는 리턴타입이야.. void랑은 살짝 다른느낌이다. void는 정말 "아무것도 리턴하지않는 것"이고, nerver는 "리턴을 할수도 있는데~ 안(못)하는거야~" 같은 느낌.
즉 위에서 설명한 바디는 뷰를 리턴해야되고 그 바디는 또 뷰 타입이라 뷰를 리턴해야되고 그속에 바디는또.... 웅앵.. 이런 재귀호출 형식이 Never Type을 만나면 끝나게 되는 것이다.
즉!! 다시 위에 컨텐츠뷰를 설명하자면, 컨텐츠뷰는 뷰 프로토콜을 준수하므로, body를 구현해야 한다. 디폴트 프로젝트에서 body는 View protocol을 준수하는 Text를 리턴하지만, 텍스트 자체의 바디는 Nerver type이므로 더이상 리턴할것이 없다~ 여서 재귀 호출이 끝난다는 말.
3. UI 꾸며보기
font와 foreground라는 수식어를 적용해서 색상과 크기를 바꾼 모습이다. 실제로 해보면.. 프리뷰에 반응이 즉각적으로 온다.. 완전신기;
요기서 완전 흥미로운점은.. 저기서 사용한 Text의 font, foreground등의 정의를 들어가보면 아래와같이 Text extension내부에 선언되어있다.
/// Sets the color of this text.
///
/// - Parameter color: The color to use when displaying this text.
/// - Returns: Text that uses the color value you supply.
public func foregroundColor(_ color: Color?) -> Text
/// Sets the font to use when displaying this text.
///
/// - Parameter font: The font to use when displaying this text.
/// - Returns: Text that uses the font you specify.
public func font(_ font: Font?) -> Text
오... foregroundColor도, font도.. 아니 그 외에 Text에 뭔 짓을 할수있는 애들은 전부다 Text 를 리턴한다!!!!!
엥... 그럼 컨텐츠뷰 코드를 다시 보자.
struct ContentView: View {
var body: some View {
Text("Hello, World!")
.font(.largeTitle)
.foregroundColor(.red)
}
}
즉 Hello, World! 라는 텍스트 자체를 Text로 리턴하면, 이 Text를 font()에서 largeTitle을 입혀서 다시 Text로 리턴하고, 이걸 다시 foregroudColor에서 red color를 입혀서 Text로 리턴한다...!!! 라는 말. 결국 Text라는 타입의 "뷰"를 계속 생성하는것이다.
오오... 아직 느낌이 빡!! 오지는 않지만 대충 어떤건진 알겠다... *_* 예제들 만들면서 조금씩 더 봐보자~.
'ios > SwiftUI' 카테고리의 다른 글
#4 SwiftUI의 Coordinator (2) | 2020.08.25 |
---|---|
#3 SwiftUI에서 Swift UIKit 사용하는것에 대해 - UIRepresentable (0) | 2020.08.14 |
#2 SwiftUI - View Layout (0) | 2020.06.07 |