반응형

 

Hacking with Swift 사이트의 강좌 번역본입니다.

 

[원문 : https://www.hackingwithswift.com/quick-start/swiftui/swiftui-tips-and-tricks]

 

SwiftUI tips and tricks

 

SwiftUI는 강력한 헤드라인 기능이 포함되어 있지만, 더 좋은 앱을 작성하는데 도움이 될 수십개의 작은 팁과 비법이 있습니다.

지금까지 접해왔던 모든 팁들을 아래에 요약하려고 노력했으며, 추가적인 컨텍스트를 제공하는데 도움을 주기 위해, 보다 더 깊히 있는 SwiftUI 튜토리얼에 대한 링크도 제공했습니다. 

실시간 미리보기 다시시작하기(Resume the live preview)

코딩하는 동안에 레이아웃을 실시간으로 미리보기 하는 것은 Xcode의 큰 기능이지만, 종종 많은 것이 변경되어 Xcode가 그것을 유지할 수 없기에, 일시중지되는 거을 보게 될 것입니다.

 

Resume을 누르기 위해 트랙패드를 끊임없이 사용하기 보다는, 여기에 SwiftUI 개발에 대한 가장 중요한 키보드 단축키가 있습니다: 미리보기 윈도우를 즉시 다시 불러오고, 실시간(live) 업데이트를 다시 시작하기 위해 Option-Cmd=P를 누릅니다.

@State를 비공개로 만들기 (Make @State private)

Apple은 앱에서 상태를 사용하는 3가지 방법을 제공합니다: @State는 단순한 지역(local) 프로퍼티 용도이고, @ObservedObject는 복잡한 프로퍼티 또는 뷰간에 공유하는 프로퍼티 용도이고, @EnvironmentObject는 많은 뷰에서 간접적으로 공유할 가능성이 있는 프로퍼티 용도입니다. 

 

@State는 지역(local) 뷰에서 사용하도록 특별하게 설계되었기 때문에, Apple은 @State 프로퍼티를 다른 곳에서 사용하지 않도록 설계된 것을 보강하기 위해서 private 표시하는 것을 권장합니다. 

@State private var score = 0

 

상수 바인딩하는 프로토타입(Prototype with constant bindings)

디자인을 하려하고 텍스트 필드와 슬라이더와 같은 것을 사용하기 위해 바인딩을 만드는 것을 원치 않는 경우, 대신 상수 바인딩을 사용할 수 있습니다. 실제 값으로 객체가 사용할 수 있습니다.

 

예를들어, 다음은 상수 문자열 Hello로 텍스트 필드를 만듭니다.

TextField("Example placeholder", text: .constant("Hello"))
    .textFieldStyle(RoundedBorderTextFieldStyle())

 

그리고 다음은 상수 값 0.5로 슬라이더를 만듭니다.

Slider(value: .constant(0.5))

 

 : 상수바인딩은 테스트와 보여주는(illustration) 목적으로만 사용합니다 - 런타임에 그것을 변경할 수 없습니다.

테스트 뷰 표현하기(Presenting test views)

프로토타이핑 하는 동안 다른 유용한 것은 상세 뷰가 아닌 모든 종류의 뷰를 표현할 수 있습니다 - 네비게이션으로 작업할때도 마찬가지입니다.

 

예를들어, 사용자의 목록이 있고 그것들중 하나를 탭하는 것을 확인하는 경우, 다음과 같이 본격적인 사용자정의 뷰 대신에 텍스트 뷰를 가리키는 네비게이션 링크를 사용할 수 있습니다.

struct ContentView: View {
    let users = (1...100).map { number in "User \(number)" }

    var body: some View {
        NavigationView {
            List(users, id: \.self) { user in
                NavigationLink(destination: Text("Detail for \(user)")) {
                    Text(user)
                }
            }.navigationBarTitle("Select a user")
        }
    }
}

 

실제 상세 뷰 디자인하기 전에 화면을 환성할 수 있습니다.

뷰 10개 제한 넘기기(Go past the 10 view limit)

SwiftUI에 있는 모든 컨테이너는 반드시 10개 이하의 자식(child)을 반환해야 하며, 보통 대부분의 용도로 괜찮습니다. 하지만, 10개 이상의 뷰가 필요한 경우, body 프로퍼티에 2개 이상의 뷰를 반환해야 하는 경우, 여러가지 다른 종류의 뷰를 반환해야 하는 경우에, 다음과 같이 그룹(group)을 사용해야 합니다.

struct ContentView: View {
    var body: some View {
        List {
            Group {
                Text("Row 1")
                Text("Row 2")
                Text("Row 3")
                Text("Row 4")
                Text("Row 5")
                Text("Row 6")
            }

            Group {
                Text("Row 7")
                Text("Row 8")
                Text("Row 9")
                Text("Row 10")
                Text("Row 11")
            }
        }
    }
}

 

Group은 순수하게 지역적인 컨테이너입니다 - 레이아웃에 영향을 주지 않습니다.

의미있는 색상 사용(Use semantic colors)

SwiftUI는 테마가 있는 사용자 인터페이스를 즉시 사용할 수 있도록 설계되었으며, 기본적으로 의미(semantic)있고 맞춤(adaptive) 색상 모두를 제공합니다. 비록 나만의 사용자정의 색상을 사용하고 싶을수 있지만, 최소한 기본 SwiftUI 세트에 무엇이 있는지 먼저 확인해야 합니다.

 

예를들어, Color.red는 순수한 RGB(255, 0, 0)이 아니지만, environment에 따라 약간 밝거나 약간 어둡습니다 - 우리가 그것을 생각할 필요없이 자동으로 적응합니다. 

 

비슷하게, Color.primary, Color.secondary, Color.accentColor 모두 environment에 의해 제공되는 고정된 값을 참조하며, 표준화된 방식으로 컨텐츠를 구성하고 강조할 수 있습니다. 

적응형 패딩에 의존하기(Rely on adaptive padding)

SwiftUI는 다음과 같이, 뷰 주변에 얼마만큼의 패딩(padding)을 적용해야 하는지를 정확하게 제어합니다.

Text("Row 1")
    .padding(10)

 

바로 정확하게 가져오기 처럼 패딩(padding)을 항상 제어하고픈 유혹에 빠지는 동안, 아무런 매개변수 없이 padding() modifier를 사용하는 경우에 적응형(adaptive) 패딩을 사용하게 됩니다 - 패딩은 컨텐츠와 환경에 따라 자동으로 스스로를 조정합니다. 

 

따라서, 앱이 일반(regular) 크기 클래스로 iPad에서 실행하는 경우 사용자가 스플릿(split) 뷰로 이동 할때보다 더 많은 패딩이 제공될 것입니다 - 코드를 작성하지 않아도 됩니다.

텍스트 뷰 결합하기(Combine text views)

+를 사용해서 여러개의 작은 것들로 새로운 텍스트 뷰를 만들 수 있으며, 더 고급 형태로 만드는 쉬운 방법입니다. 예를들어, 서로 다른 색으로 3개의 텍스트 뷰를 만들고 그것들을 함께 결합합니다. 

struct ContentView: View {
    var body: some View {
        Text("Colored ")
            .foregroundColor(.red)
        +
        Text("SwifUI ")
            .foregroundColor(.green)
        +
        Text("Text")
            .foregroundColor(.blue)
    }
}

 

print() 작업 방법

디자인을 살펴보기 위해서 SwiftUI 미리보기에서 play를 누르는 경우, print() 호출은 무시됩니다. 테스트 목적으로 print()를 사용하는 경우(예를들어, 간단한 버튼 탭 동작)에는 정말로 머리가 아플수 있습니다.

 

다행히도, 이런것은 간단하게 고칩니다: 미리보기 캔버스에 있는 play 버튼에서 우측 클릭하고 Debug Preview를 고릅니다. 작은 변화로 print() 호출 작업이 정상적으로 작동하는 것을 알게 될 것입니다.

암시적인 HStack에 의존하기(Relying on the implicit HStack)

항목들의 목록을 만들때, 왼쪽에 이미지가 있고 오른쪽에 텍스트가 있는 iOS 표준 모양을 원하는게 일반적입니다.

 

흠, 동적인 항목들의 목록을 사용하는 경우 실제로 목록 내부에서 무료로 HStack을 제공하므로, 직접 만들 필요가 없습니다. 

 

따라서, 다음 코드는 배열로부터 사진 이름을 기반으로 목록을 만들고, 이미지와 텍스트를 나란히 정렬하기 위해서 암시적으로 HStack에 의존합니다.

struct ContentView: View {
    let imageNames = ["paul-hudson", "swiftui"]

    var body: some View {
        List(imageNames, id: \.self) { image in
            Image(image).resizable().frame(width: 40)
            Text(image)
        }
    }
}

 

큰 뷰 분할하기(Splitting up large views)

큰 뷰를 가지고 있는 경우 같은 결과를 얻기위해, 몇개의 더 작은 뷰로 나누고 그것들을 함께 구성하는 것이 더 쉽다는 것을 알게 될 것입니다. SwiftUI의 커다란 기능중 하나는 뷰 계층구조가 단조롭기(flattens) 때문에, 성능차이가 없다는 것이지만, 확실히 유지보수를 쉽게 해준다는 것입니다!

 

예를들어, 다음은 모든 사용자에 대한 이미지, 제목, 부제목을 보여주는 목록입니다.

struct ContentView: View {
    let users = ["Paul Hudson", "Taylor Swift"]

    var body: some View {
        NavigationView {
            List(users, id: \.self) { user in
                NavigationLink(destination: Text("Detail View")) {
                    Image("example-image").resizable().frame(width: 50, height: 50)

                    VStack(alignment: .leading) {
                        Text("Johnny Appleseed").font(.headline)
                        Text("Occupation: Programmer")
                    }
                }
            }.navigationBarTitle("Users")
        }
    }
}

 

비록 그렇게 복잡하지는 않지만, 무슨일이 일어나는지 이해하려면 주의해서 읽어야 합니다.

 

다행히도, 이해하기 쉽고 재사용하기 쉽도록 뷰의 일부를 별도의 뷰로 가져올수 있고, Xcode에서는 아주 쉽습니다: 네비게이션 링크에서 Cmd+클릭하고 Extract Subview를 선택하면 됩니다. 코드 밖의 새로운 SwiftUI 뷰로 끌어내고(pull), 원래 있는 곳에는 참조로 남아있습니다. 

 

주의: 하위뷰가 부모의 데이터를 의존하는 경우에 스스로 전달해야 합니다. 

더 나은 미리보기(Better previewing)

SwiftUI의 많은 혜택중 하나는 작업하면서 레이아웃을 바로 미리보기 할 수 있다는 것입니다. 더 좋은것은, 이러한 미리보기를 사용자정의 할 수 있으므로 여러개의 디자인을 나란히 볼수 있고, 네비게이션 뷰를 사용해서 어떻게 보이는지 확인하고, 다크모드를 사용해 볼수 있습니다.

 

예를들어, 다음은 3개의 다른 디자인을 나란히 보여주는 ContentView 미리보기를 만듭니다: 초대형 텍스트, 다크모드, 네비게이션뷰

#if DEBUG
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
                .environment(\.sizeCategory, .accessibilityExtraExtraExtraLarge)
            ContentView()
                .environment(\.colorScheme, .dark)
            NavigationView {
                ContentView()
            }
        }
    }
}
#endif

 

: 미리보기 윈도우에서 축소하거나 스크롤해서 모든 다른 미리보기를 확인하세요.

사용자정의 modifiers 만들기(Create custom modifiers)

같은 뷰 modifiers(예를 들어, 텍스트뷰에 패딩이 있고, 특정 크기이며, 배경색과 전경색을 고정하도록 만드는 경우)의 세트를 정기적으로 반복하는 것을 알게 되는 경우에 코드가 반복되는 것보다는 사용자정의 modifier로 바꾸는 것을 고려해야 합니다. 

 

예를들어, 패딩을 추가하고, 검정 배경, 흰색 텍스트, 큰 폰트, 모서리가 둥근 새로운 PrimaryLabel modifier를 만듭니다.

struct PrimaryLabel: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.black)
            .foregroundColor(.white)
            .font(.largeTitle)
            .cornerRadius(10)
    }
}

 

다음과 같이, 이제 .modifier(PrimaryLabel())을 사용해서 모든 뷰에 첨부할 수 있습니다.

struct ContentView: View {
    var body: some View {
        Text("Hello World")
            .modifier(PrimaryLabel())
    }
}

 

애니메이션을 쉽게 변경하기(Animate changes easily)

SwiftUI는 뷰 계층구조에서 애니메이션을 변경하는 2가지 방법이 있습니다: animation() withAnimation(). 그것들은 다른곳에 사용되지만, 둘다 앱에 있는 뷰에 변경사항을 부드럽게 만드는 효과가 있습니다.

 

animation() 메소드는 바인딩에서 사용되고, 바인딩한 값이 수정되어 모든 변경사항을 애니메이션하도록 SwiftUI에게 요청합니다. 예를들어, 다음은 라벨을 보여주거나 숨기는 Toggle을 가진 뷰입니다.

struct ContentView: View {
    @State private var showingWelcome = false

    var body: some View {
        VStack {
            Toggle(isOn: $showingWelcome) {
                Text("Toggle label")
            }

            if showingWelcome {
                Text("Hello World")
            }
        }
    }
}

 

토글이 변경될때, 아래의 텍스트 뷰는 즉시 나타나거나 사라질 것이며, 좋은 경험은 아닙니다. 하지만, animation()을 사용하는 경우 토글이 변경될때 부드럽게 밀어넣고 빼내도록 만들 수 있습니다.

Toggle(isOn: $showingWelcome.animation()) {
    Text("Toggle label")
}

 

다음과 같이, 원하는 종류의 애니메이션를 제어할 수도 있습니다.

Toggle(isOn: $showingWelcome.animation(.spring())) {
    Text("Toggle label")
}

 

바인딩 보다는 일반적인(regular) 상태로 작업할때, withAnimation() 호출에서 변경 사항을 랩핑(wrapping)해서 애니메이션 할 수 있습니다.

 

예를들어, 다음은 버튼 누르기를 사용해서 환영 라벨을 표시하거나 숨기는 것을 제외하면 같은 뷰입니다.

struct ContentView: View {
    @State private var showingWelcome = false

    var body: some View {
        VStack {
            Button(action: {
                self.showingWelcome.toggle()
            }) {
                Text("Toggle label")
            }

            if showingWelcome {
                Text("Hello World")
            }
        }
    }
}

 

이전과 마찬가지로 환영 라벨이 나타나고 즉시 사라질 것이지만, 변경하는 것을 withAnimation()으로 감싸는 경우에 대신 애니메이션 할 것입니다.

withAnimation {
    self.showingWelcome.toggle()
}

 

그리고 animation()과 정확히 같은 방식으로 사용자정의 할 수 있습니다.

withAnimation(.spring()) {
    self.showingWelcome.toggle()
}

 

뷰에서 여러개의 경고창 보여주기(Showing multiple alerts in a view)

여러개의 alert() modifiers를 단일 뷰에 첨부하려는 경우, 코드가 예상했던 것처럼 동작하지 않습니다 - 하나의 경고창은 동작핳것이지만 다른것들은 동작하지 않습니다.

 

이를 고치려면, 경고창이 표시되도록하는 버튼이나 다른 뷰와 같은, 뷰 계층구조의 다른 부분에 경고창을 첨부해야합니다.

바인딩으로 새로운 값 배포하기(Publishing)

마지막으로, publisher로부터 업데이트 알림을 보낼때 문제를 피하기 위해(예를들어, PassthroughSubject에서 send() 호출하기 하거나 모든 @Published 프로퍼티 업데이트하기) 항상 메인 스레드에 있어야 합니다.

 

UIKit과 대부분의 다른 UI 프레임워크와 마찬가지로, SwiftUI 앱에서 원하는 모든 것을 백그라운드에서 작업할 수 있지만, 사용자 인터페이스를 조작하는 것은 메인 스레드에서만 해야 합니다. 왜냐하면 상태 변경이 자동으로 body를 갱신하도록 하기 때문에, 해당 상태 변경을 메인 스레드에서 실행하도록 만드는 것은 중요합니다. 

반응형
Posted by 까칠코더
,