SwiftUI 스터디 2 - TextFiled, Section, Picker, Keyboard dismiss

2021. 5. 24. 17:59Developer.TokkiSea/Apple

반응형

 

1편에 이어서

 

SwiftUI 스터디 겸

메모 겸 쓰는 글이라서

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

있을 수 있습니다.

 

오늘 첫 번째 전체 소스입니다.

대충 보시고 아래 설명을 먼저 보세요.

import SwiftUI

struct ContentView: View {
    @State var phoneNumberString = ""
    @State var nameString = ""
    @State var age = 0
    @State var 선택한동물 = ""
    @State var isEditing = false
    let 동물 = ["강아지","고양이","토끼"]
    init() {
        UISegmentedControl.appearance().selectedSegmentTintColor = .blue
        UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.white], for: .selected)
        UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.red], for: .normal)
    }
    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("Input"), content: {
                    TextField("name", text: $nameString) { editing in
                        isEditing = editing
                    } onCommit: {
                    }
                    TextField("phone number", text: $phoneNumberString)
                        .keyboardType(.numberPad)
                    
                    Picker("Age", selection: $age) {
                        ForEach(0..<80)
                        {
                            Text("\(String($0)) 살")
                        }
                    }
                    Picker(selection: $선택한동물, label: Text("좋아하는 동물을 선택하세요."), content: {
                        ForEach(0..<동물.count)
                        {
                            Text("\(동물[$0])").tag("\(동물[$0])")
                        }
                    })
                    .pickerStyle(SegmentedPickerStyle())
                })
                SubView(name:$nameString, phoneNumber: $phoneNumberString, age: $age, 선택한동물: $선택한동물)
            }.navigationTitle("Hello TokkiSea")
            .gesture(TapGesture().onEnded({
                        UIApplication.shared.windows.first{$0.isKeyWindow }?.endEditing(true)
            }), including: isEditing ? .all : .none)
        }
    }
}
struct SubView: View {
    @Binding var name : String
    @Binding var phoneNumber : String
    @Binding var age : Int
    @Binding var 선택한동물 : String
    var body: some View {
        Section(header: Text("Output")) {
            Text("Name : \(name)\nPhoneNumber : \(phoneNumber)")
            Text("Age : \(age)")
            Text("선호하는 반려동물 : \(선택한동물)")
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

일반 키보드, 숫자형태의 키보드

 

 

 

@State var 선택한 동물 = ""

let 동물 = ["강아지", "고양이", "토끼"]

배열은 간단하게 이런 식으로 사용하고요.

 

TextField에 키보드 타입을 정할 수 있습니다.

TextField("phone number", text: $phoneNumberString)

.keyboardType(.numberPad)

이런 식으로 숫자패드 등 여러 가지가 있어요.

 

그런데 입력한 후 다른 무슨 행동을 해도 키보드가 내려가지 않을 겁니다.

그래서 조금 다른 방식으로 TextField를 사용해 봅니다.

 

@State var isEditing = false

 

TextField("name", text: $nameString) { editing in

isEditing = editing

} onCommit: {}

.keyboardType(.default)

 

저도 이렇게 쓰는 게 깔끔한 건진 잘 모르겠지만

찾아본 것 중에 가장 깔끔해 보입니다.

먼저 플래그를 하나 만들어주시고

TextField 사용 방식을 OnChanged 방식으로 사용해 봅니다.

아시겠지만 자동으로 완성된 후 Tab을 누르면 수정해야 하는 부분을

하나씩 넘어가게 되고 엔터를 누르면 입력할 수 있겠죠?

StringProtocol 은 그냥 "텍스트" 입력이고

Binding <String>는 얼핏 보면 헛!! 할 수 있지만..;;

이전 편에서 보셨듯이 바인딩해주면 됩니다.

$nameString 이거요.

그다음 onEditingChanged: (Bool) -> Void 도 그냥 엔터 쳐보시면

{ Bool in

...

}

으로 나오고요. Bool 도 엔터 쳐서 사용할 변수명을 정해줍니다.

onCommit도 동일하게요.

이제 완성된 모습은 "OnEditingChanged"라는 표현이 없어요.

그래서 저 Bool 변수에 "onEditingChanged"라고 적어놔도 좋을 것 같네요.

 

이제 Form의 맨 아래에 붙어 있는

.gesture(TapGesture().onEnded({

                        UIApplication.shared.windows.first{$0.isKeyWindow }?.endEditing(true)

            }), including: isEditing ? .all : .none)

이 코드가 키보드를 내려주는 기능입니다.

그런데 including: 에서 isEditing를 사용하고 있어요.

이렇게 하는 이유는 모든 탭 제스처를 잡아서 키보드 내려주는 기능인데

이렇게 해버리면 탭이 필요한 기능(버튼 등등)이 기능들 모두가 먹통이 돼버립니다.

얘네들이 안눌러짐.

 

그래서 "무언가 편집이 되었다" 일 때만. all가 적용되어서

키보드가 내려가게 되지요.

아닐 때는 해당 기능이 먹히도록 해야 합니다.

 

 

다음은 픽커입니다.

 

Picker("Age", selection: $age) {

ForEach(0..<80) {

Text("\(String($0)) 살")

}}

기본 픽커인데 이걸 누르면 아래처럼 선택할 수 있는 리스트가 나옵니다.

 

ForEach(0..<80) {) 는 0부터 79까지 반복하고

해당 숫자로 0살~79살까지 텍스트를 만들어 줍니다.

리스트에서 선택하게 되면 순수 번호가 나옵니다.

첫 번째는 0, 두 번째는 1...

ForEach(20..<100) {)

을 해도 첫 번째는 0, 두 번째는 1 이예요. 20, 21 이 아니고요.

참고로 ForEach(20..<100) {) 위나 아래에

Text("120살")처럼 따로 추가되는 건 선택이 안됩니다.

(왜 그럴까요;; - 나중에 알게 되겠죠;;)

 

 

이번에는 버튼식으로 누를 수 있는 픽커입니다.

                    Picker(selection: $선택한동물, label: Text("좋아하는 동물을 선택하세요."), content: {

ForEach(0..<동물.count) {

Text("\(동물[$0])").tag("\(동물[$0])")

}})

.pickerStyle(SegmentedPickerStyle())

"동물"은 미리 만들어둔 배열입니다.

 

let 동물 = ["강아지","고양이","토끼"]

 

동물.count 는 배열 개수이겠죠?

그렇게 ForEach 내에서는 $0 식의 배열 값을 참조할 수 있습니다.

$0 = 0, 1, 2로 값이 나오고 [0], [1], [2] 식으로 참조하는 거죠.

(이런 내용들은 Swift를 처음 접하시는 분들을 위해 한번 정도만 쓰겠습니다.)

 

자 이제 픽커 형태를 바꾸는 부분은

.pickerStyle(SegmentedPickerStyle()) 이겁니다.

SegmentedPickerStyle()를 찾아가 보면 다른 종류들의 픽커가 많이 나옵니다.

필요한 걸 찾아서 한번 해보시고, 소스 내부에 설명도 있으니 참조해보세요.

 

이제 선택을 하면 $선택한동물에 텍스트로 저장됩니다.

label: 은 위의 기본 픽커의 제목으로 사용되는데

지금이건 버튼식이라 제목이 안 나와요.

 

Section

폼을 구분하고 주제를 보여주는 섹션입니다.

Section(header: Text("Input"), content: {

})

폼이 훨씬 깔끔하게 됩니다.

 

그리고 버튼 선택 방식 픽커 버튼의 글자 색과 선택 색이 조금 다르게 나오고 있어요.

이건

init() {

UISegmentedControl.appearance().selectedSegmentTintColor = .blue

UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.white], for: .selected)

UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.red], for: .normal)

}

이 방식으로 바꿨습니다.

지금은 모든 SegmentedPickerStyle 형태의 색을 다 바꿔버리는데

다음에 개별적으로 바꾸는걸 한번 찾아보겠습니다.

 

이제 위에 입력된 내용을

SubView(name:$nameString, phoneNumber: $phoneNumberString, age: $age, 선택한동물: $선택한동물)

별도로 만든 뷰로 양방향 바인딩해서 전달하고

OUTPUT 내용을 출력합니다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

TextFiled, Section, Picker, Keyboard dismiss

 

반응형