SwiftUI 스터디 1 - Text, Button, From, Navigation, @State, @Binding

2021. 5. 12. 17:20Developer.TokkiSea/Apple

반응형

 

저도 묵혀두었던 SwiftUI 스터디 겸

메모겸 쓰는 글이라서

전문적이지 못하고 설명이 틀린 부분이

있을 수 있습니다.

 

 

프로젝트 생성

MultiPlatform(iOS, MacOS합본), iOS, MacOS

중에 하나를 생성합니다.

MultiPlatform은 iOS, MacOS 공통으로

공유하는 형식의 파일이 생성됩니다.

Intel 계열에는 부적합하겠지만

향후 대부분 M1 맥북 사용을 고려한다면

괜찮은 방법입니다.

 

프로젝트 명은 "TokkiSeaUI"로

생성하겠습니다.

 

TokkiSeaUIApp.swift

import SwiftUI
@main
struct TokkiSeaUIApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

말 그대로 만들어야 할 App의 Main입니다.

(AppDelegate가 어디 갔지?;;)

ContentView()를 호출하고 있습니다.

 

ContentView.swift

struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
            .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

body는 필수로 있어야 하고

최상위 뷰 역할을 합니다.

아래 부분 ContentView_Previews로 인해

XCode 에디터 우측에 Playground 같은 녀석을

보여주게 됩니다.

TokkiSeaUIApp.swift에서 ContentView()를

주석 처리한 후 실행해보면 UI가 안 나옵니다.

즉, Previews 미리보기만을 위한 녀석이고

실제 앱은 TokkiSeaUIApp.swift에 코딩된 데로

실행 됩니다.

그러니 Previews에서 TokkiSeaUIApp.swift에

넣을 UI 코드를 미리 작성해보는 거지요.

 

오른쪽에 Resume를 눌러보면

"Hello, world!"가 보이는 UI 가 미리 보입니다.

코딩할 때마다 자동으로 UI가 바뀌지요.

(좋은 세상이네요.)

 

struct ContentView: View {
    var body: some View {
          Text("Hello, world!")
          Text("Hello, world!2")
          Text("Hello, world!3")
    }
}

 

계속해서 Text("Hello, world!")를 여러 개 복사해서

넣어보면 하나밖에 안보입니다.

body view에 Text View 하나만 차지하게 되네요.

 

Form

으로 감싸 보면 모두 보입니다.

.padding()을 넣으면 Text View에 패딩을 줘서

간격이 늘어납니다.

기본적으로 .padding([.leading]) : 왼쪽 공간을 띄워줌

인 것 같네요.

[.leading, .trailing, .top, .bottom] : 왼쪽, 오른쪽, 위, 아래

.top을 주면 윗부분이 띄워져서 Hello, world! 는

아래쪽으로 치우쳐집니다.

[] 안에서 .(쩜)을 찍어보면 horizontal 등

익숙한 정렬 애들이 나오고

.all 이라는 상하좌우 다 주는 아이도 있네요.

 

.padding()와 동일한 방식으로

또 .(쩜)을 찍어서 font, backgroundColor, ForegroundColor

등등 예상되는 기능들을 사용하시면 됩니다.

 

some 은 좀 길게 다음에 설명하고

간단하게 설명하면

알 수 없는(불투명한) 타입으로 리턴할 수 있습니다.

리턴 타입을 작성하지 않아도 되고요.

즉,

View에 리턴 타입을 Func1, Func2가 만들어졌다고 하고

some 붙여서 View를 상속해 사용하면 return Func1()이나

return Func2() 아무거나로 리턴할 수 있게 됩니다.

 

아직 활용을 안 해봐서

왜 좋은지 모르겠지만

좋은 느낌?

 

From {} 안에 Text View를 10개 이상 쓰면 에러가 나요;;;

굳이 왜;;

여하튼 Group {}로 묶어주면 됩니다.

NavigationView로 감싸주면

기존에 많이 써왔던 NavigationView가 만들어집니다.

View들을 탐험하는 형태로 리스트에 항목을 누르면

안으로 들어가서 또 다른 View가 나타나고 다시

뒤로 갈 수 있는 기능을 만들기 위한 그것 입니다.

 

타이틀 제목은 Form {} 뒤에

.(쩜) 을 찍고 navigationTitle()를 적어줍니다.

혹시 {} 뒤에 .(쩜) 찍는 게 특이하신가요?

코드를 자세히 보시면

클래스가 아니고 모두 struct로 되어 있습니다.

온통 구조체입니다.

 

View내부에는 body를 꼭 구현해주세요~~ 라는

내부 설명이 붙어 있습니다.

Reactive X 가 나온 이후 SwiftUI도 아예 똑같은 방식을

도입하게 되었네요.

 

 

이 쯤되면

버튼도 Button도 Text와 비슷하게 쓸 수 있지 않을까 하네요.

처리부는

{} 요걸로 하면 될 것 같습니다.

 

 

일단 SwiftUI에서 .(쩜)이 어색하신분은

Reactive X 중에 RxSwift(Reactive Swift)를

어느 정도 이해 하도록

먼저 공부해보시기를 추천드립니다.

아무래도 Text(""). padding(). padding(). padding()........

이런 방식이 생소할 수도 있을 겁니다.

그리고 나중에 내용이 어려워지면 이해가 어려워 질 수 있습니다.

 

var count : Int = 0

을 선언하고 버튼 누를 때마다 증가를 시켜줍니다.

근데 구조체 내부에서는

값이 변할 수 없습니다.

그래서 에러가 발생합니다.

@State var count : Int = 0

으로 @State를 넣어줍니다.

선언된 View 내부에서만 사용할 수 있고

View 관련 데이터를 저장하기 위해 사용합니다.

 

.self가 없으면 에러가 나와야 하는데 괜찮군요;;

 

이 @State 로 된 값을 다른 뷰가 참조하게 할 수도 있습니다.

 

@State var currentString를 선언하고

TextField에 입력되는 텍스트를 저장하게 해 줬습니다.

에러가 발생합니다.

@State 붙은 변수는 해당 뷰에서만 사용할 수 있습니다.

 

Binding<String>을 사용해서 다른 뷰에서도 사용할 수 있게 해줍니다.

에러 메시지 내용에 보면

$ 표시를 붙이면 된다고 아주 친절하게 알려주네요.

$표시를 넣어서 $currentString를 다른 뷰도 사용하도록

"바인딩해준다"가 되겠습니다.

이렇게 하면 TextField (View) 도

text라는 이름으로 연결된 currentString이

값을 쓸 수 있게 되는 "바인딩받는다"가 됩니다.

 

현재 뷰에서는 currentString를 참조할때는

바인딩을 하지 않아도 됩니다.

이쯤 되니 View 영역들 간의 데이터 흐름이

클래스와는 많이 다르다는걸 알게 됩니다.

struct ContentView: View {
    @State var count = 0
    @State var currentString = ""
    var body: some View {
        Form {
              SubView(SubcurrentString:$currentString, Subcount: $count)
        }
    }
}

struct SubView: View {
    @Binding var SubcurrentString : String
    @Binding var Subcount : Int
    var body: some View {
        Form {
            Text("binding string is \(SubcurrentString), and count \(Subcount)")
        }
    }
}

 SubView(currentString:$currentString, count: $count)

와 같이 SubView라는 다른 뷰 내에

SubcurrentString이라는 이름으로 $currentString를 바인딩해주면

SubView에서도 ContentView의 currentString를 사용할 수 있게 됩니다.

Subcount 도 마찬가지입니다.

여기서 주의할 점은

    @Binding var SubcurrentString : String = "initString"

처럼 값을 초기화하면 에러가 납니다.

바인딩받아야 하는 아이라 초기화가 필요도 없을뿐더러

혼란이 생길 수도 있습니다.

 

그리고 바인딩받은

SubcurrentString = "new Text~~" 식으로

값을 변경할 수 있을 것 같지만

이것 또한 그렇지 않습니다.

처리 부분이 아닌 뷰를 보여주는 코드들만 동작하기 때문입니다.

Button("testButton") {
	SubcurrentString = "new Text~~~" 
}

이런 식으로 특정 처리부에서 사용해야 합니다.

 

다시 말해서 View를 상속받는 구조 체내에서는

UI 만 그려야 하고 UI가 가진 처리부에서

데이터 처리를 해야 합니다.

 

나름 복잡해 보이는 이런 구조들은

값의 변경만으로 연결된 모든 뷰의

상태를 자동으로 갱신됩니다.

 

만약 덩치가 무지 큰 앱에서

어떤 값이 하나가 바뀌게 되면

기존 방식으로는 관련된 모든 클래스 며 메서드들을

하나하나 호출해서 값을 변경하도록 해야 했습니다.

(물런 안그러도록 기존에도 방법이 있었습니다만)

 

이제는 값만 바뀌도 그 값을 참조하는 모든 부분에서

자동 갱신이 일어납니다.

오히려 코딩이 쉬워지는 효과가 있고

버그 발생 가능성도 많이 줄어들게 됩니다.

 

 

 

이번 편은 여기까지 입니다.

 

 

 

 

 

 

 

반응형