반응형

 

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

 

[원문 : https://www.hackingwithswift.com/quick-start/swiftui/building-a-menu-using-list]

 

Building a menu using List

[동영상 강좌 : https://youtu.be/oJR-CH7sPxs]

 

 

간단하게 시작하고 더 작업할것입니다. 따라하면서 SwiftUI로 쉽게 만들수 있는 것과 SwiftUI로 만드는 몇 가지 것을 보게될 것입니다. 음.. 쉽지 않다라고 합시다.

 

ContentView.swift에는 우리 앱에서 유일한 화면을 표현하는 기본 구조체가 있습니다: ContentView. 다음과 같이 보입니다.

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

 

여기에 코드가 많지 않지만, 이미 많은 것을 알려주고 있습니다.

  1. SwiftUI에서 View는 구조체입니다. 이게 필수는 아니지만, 강력히 권장됩니다.
  2. 모든 뷰는 반드시 View프로토콜을 준수(conform)해야 합니다.
  3. 그 프로토콜은 뷰에 대한 실제 레이아웃을 포함하는 계산 프로퍼티 body가 필요합니다.
  4. 그것은 some View를 반환하며, 불투명 반환 타입(opaque return types)라는 Swift의 기능입니다 - 하나의 특정 뷰이지만, 실제로 어떤것이든 상관없습니다.
  5. 컨텐츠 뷰 안에는 기본 UI인 Text가 있습니다. 이것은 SwiftUI에서 문자열을 표현하는데 사용되며, 라벨(labels)처럼 또는 네비게이션 바, 버튼의 안쪽 텍스트로 간접적으로 사용합니다.

macOS Catalina에서 실행하는 경우에 Xcode의 오른쪽에 미리보기 판넬(preview pane)이 나타납니다. 이를 입력하면 업데이트 되므로, 작업하면서 변경사항을 확인하기 좋은 방법입니다. 미리보기가 멈추는 경우에(실수할때 발생할 것임) 레이아웃을 다시 그리도록하기 위해 Option-Command-P를 누를수 있습니다.

 

정말 중요해서 반복: SwiftUI 미리보기 업데이트하기 Opt-Cmd-P

 

Catalina가 설치되어 있지 않은 경우에는 미리보기를 볼수 없고 시뮬레이터에서 코드를 실행해야 합니다.

Xcode에의해 생성된 기본 컨텐츠 뷰는 단일 텍스트 Hello World입니다. 우리 앱에서는 실제로 메뉴의 목록을 보여줄 것이므로, list 뷰를 사용할 것입니다.

 

현재 Text 뷰를 다음과 같이 교체합니다.

List {
    Text("Hello World")
    Text("Hello World")
    Text("Hello World")
}

 

미리보기가 업데이트 될때 Hello World라는 3개의 텍스트로 된 UITableView와 같은것을 보게 될 것입니다. 이는 정적인(static) list 뷰 입니다 - 이는 3개의 고정된 데이터를 보내고, 테이블(table)에 3개의 행(row)으로 해석됩니다.

 

우리 앱의 메뉴는 주문할수 있는 항목들을 포함할 것이고, 하나를 탭해서 주문 메뉴에 대한 새로운 상세화면을 보여줄 것입니다. 이는 UIKit에서 하는 것과 같습니다: 우리는 네비게이션 컨트롤로 테이블을 감쌉니다(wrap).

 

SwiftUI에서 네비게이션 컨트롤은 NavigationView이며, NavigationBar에서 보여지는 스타일과 UINavigationController의 뷰 컨트롤러 스택 동작을 결합한 것입니다. 하나로 사용하려면 다음과 같이, 그냥 list 주위에 NavigationView를 추가하세요.

NavigationView {
    List {
        Text("Hello World")
        Text("Hello World")
        Text("Hello World")
    }
}

 

미리보기 업데이트 되면 list 위의 넓은 공간이 보이게 될 것이며, 네비게이션 바가 있는 곳입니다. 하지만, 제목(title)을 지정하지 않았기에 비어있습니다.

 

이를 고치기 위해서 SwiftUI에서 수정자(modifiers)라고 하는 중요한 것을 배워야 합니다. 이것은 Swift의 일반적인 메소드처럼 보이지만, 실제 적용되는 대상을 변경하기 때문에 더 복잡합니다. 간단히 말해서, 텍스트에 foregroundColor() modifier을 적용하는 경우, 단순히 색상이 있는 텍스트를 가져오는것이 아닙니다 - 실제로 다른 타입(type)을 가져옵니다.

 

이 경우에, 네비게이션 바에 보여지는 텍스트를 허용하는 navigationBarTitle() modifier을 우리의 list 뷰에 적용합니다. 다음과 같이 작성됩니다.

NavigationView {
    List {
        Text("Hello World")
        Text("Hello World")
        Text("Hello World")
    }
    .navigationBarTitle("Menu")
}

 

navigation view가 아닌 list에 modifier를 붙입니다 - UINavigationController의 제목을 설정하는것 대신에 UIViewController의 제목을 설정하는 방법을 생각해보세요.

지금 앱을 실행하려는 경우에 예상한데로 정확하게 모두 동작하는 것을 보게 될 것입니다 - 테이블이 스크롤되고, 스크롤 네비게이션 막대가 작아집니다. SwiftUI의 중요한 것 하나는 기본적으로 현대적인 시스템 동작을 제공하므로, 기본적으로 큰 네비게이션 바 제목을 가집니다.

 

고정된 테이블 셀을 가지고 있을때 정적인(Static) 텍스트 작업이 좋지만, 우리의 경우에는 섹션별로 가지고 와야할 많은 메뉴가 있습니다 - 아침, 메인, 디저트, 음료. 우리가 정말로 원하는 것은 JSON으로 메뉴 데이터를 가져와서, 목록에 사용하는 것이고, 실제로 하는 것이 어렵지 않습니다.

 

먼저 데이터를 가져와야 합니다. Helper.swift 파일은 이미 앱 번들에서 Codable JSON을 가져오기 위한 코드가 포함되어 있으며, menu.json 파일을 가져오기에 완벽합니다. 이제 ContentView구조체에 프로퍼티를 추가하세요.

let menu = Bundle.main.decode([MenuSection].self, from: "menu.json")

 

다음으로 필요한것은 목록에서 메뉴에 있는 섹션을 처리하는 것입니다. 이것은 ForEach블록을 사용해서 완료하며, 배열에 있는 항목들을 반복하고 안쪽의 모든것을 반복합니다.

List {
    ForEach(menu) {
        Text("Hello World")
        Text("Hello World")
        Text("Hello World")
    }
}

 

 

List ForEach 뒤의 열린 중괄호는 실제로 클로져의 시작을 나타내고, SwiftUI ForEach는 배열에서 각 섹션을 클로져로 전달해서 구성할 수 있습니다.

 

다음과 같이 코드를 수정해서 해당 섹션을 적용해야 합니다.

ForEach(menu) { section in

 

 

거의 동작하지만, 마지막으로 해야할게 하나 남았습니다. SwfitUI는 테이블(table)에 있는 모든 셀(cell)을 식별하는 방법을 알아야 합니다 - 어떤 것인지 정확히 알아야 하므로, 요청에 따라 추가하고 제거할 수 있습니다. 정적 목록이 있을때는 3개가 있다는 것을 알수 있었기에 문제가 되지 않았지만, 지금은 동적인 목록이므로 각 섹션마다 고유한 것에 대해서 알려줘야 합니다.

 

Menu.siwft을 열어서 MenuSection MenuItem이 정의된 구조체들과, 둘 다 UUID(universally unique identifier)를 포함하는 id 프로퍼티를 가지고 있는 것을 알게 될 것입니다. 이것은 우리가 사용하기에 완벽하며, 모든 섹션의 메뉴 항목에는 고유한 식별자가 있으므로 SwiftUI는 어떤 것인지 알 수 있습니다.

 

SwiftUI에 Identifiable를 준수하는 2가지 타입을 만들어서 이러한 식별자로 사용하도록 할 수 있습니다. 이 프로토콜에는 하나의 요구사항만 있으며, 이를 준수하는 타입은 고유하게 식별할 수 있도록 id라는 프로퍼티를 가져야 합니다. 우리는 이미 가지고 있으므로, 이러한 2개의 타입에 Identifiable을 추가하는 것만으로도 충분합니다.

struct MenuSection: Codable, Identifiable {

struct MenuItem: Codable, Equatable, Identifiable {

지금 코드를 실행하는 경우에 Hello World를 포함하는 12개의 행(rows)을 보게 될것입니다 - 간혹 예상치 못하기도 합니다.

 

바뀐것은 이제 동적인(dynamic) 목록을 가지고 있는 것이고, ForEach는 클로져의 본문에서 메뉴 섹션에 있는 모든 항목을 한번씩 실행될 것입니다. 4개의 섹션을 가지고, 각각 3개의 텍스트 뷰를 가지므로, 총 12로 끝납니다.

 

이를 고치기 위해, 섹션당 하나의 텍스트를 사용하고, 보여줄 섹션의 이름을 지정합니다.

List {
    ForEach(menu) { section in
        Text(section.name)
    }
}

 

다음으로 각 섹션 내부의 항목들을 추가하세요. 다음과 같이 섹션 ForEach 안쪽에 또 다른 ForEach가 있습니다.

List {
    ForEach(menu) { section in
        Text(section.name)

        ForEach(section.items) { item in
            Text(item.name)
        }
    }
}

 

이제 섹션 이름(Blackfast, Mains, 등)과 메뉴 항목 이름(Full English, Superfood Salad, 등)을 포함하는 많은 테이블 행을 보게 될 것입니다.

이것은 동작하지만, 이상적이진 않습니다 - 테이블에 시각적인 구조체를 만들지 않았으므로, 이를 쪼갤것입니다. 이를 처리하는 표준 UIKit 방법은 테이블 뷰 섹션을 사용하는 것이고, SwiftUI는 이에 대해서 Section뷰를 제공합니다. Text(section.name)을 헤더용으로 Section을 사용하도록 교체할 수 있으며, 섹션을 시작하는 텍스트입니다. 안쪽에 있는 ForEach(메뉴들을 포함)은 섹션 안쪽에 있으며, SwiftUI는 함께 그룹화하는 방법을 알게 될 것입니다.

 

마지막 결과는 다음과 같습니다.

List {
    ForEach(menu) { section in
        Section(header: Text(section.name)) {
            ForEach(section.items) { item in
                Text(item.name)
            }
        }
    }
}

 

SwiftUI의 목록은 기본적으로 UITableView plain 스타일이지만, navigationBarTitle() 뒤에 또 다른 modifier를 추가해서 변경할 수 있습니다.

.listStyle(GroupedListStyle())

반응형
Posted by 까칠코더
,