관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - SwiftUI] List 사용방법 (리프레시, multiSelection, Section, Hierarchical List, ListStyle) 본문

iOS 기본 (SwiftUI)

[iOS - SwiftUI] List 사용방법 (리프레시, multiSelection, Section, Hierarchical List, ListStyle)

jake-kim 2022. 8. 25. 23:33

목차) SwiftUI의 기본 - 목차 링크

List

https://developer.apple.com/documentation/swiftui/list

  • 형태
    • Hashable을 따르고, View를 준수하는 두 개의 제네릭스로 구성
struct List<SelectionValue, Content> where SelectionValue : Hashable, Content : View
  • 사용방법은 List 후 클로저안에 subviews들을 넣어서 구현
struct ContentView: View {
  var body: some View {
    List {
      Text("A List Item")
      Text("A Second List Item")
      Text("A Third List Item")
    }
  }
}

  • List(:) { } 형태로도 사용이 가능
struct Ocean: Identifiable {
  let name: String
  let id = UUID()
}

private var oceans = [
  Ocean(name: "Pacific"),
  .init(name: "Atlantic"),
  .init(name: "Indian"),
  .init(name: "Southern"),
  .init(name: "Arctic"),
]

var body: some View {
  List(oceans) {
    Text($0.name)
  }
}

Multi Selection (라디오 버튼)

  • List에 Radio버튼처럼 구현 방법
    • NavigationView와 같이 사용
    • List에 selection 파라미터에 여러개의 selection값을 저장할 @State 프로퍼티 주입
    • toolbar로 EditButton()을 추가
@State private var multiSelection = Set<UUID>()

var body: some View {
  NavigationView {
    List(oceans, selection: $multiSelection) {
      Text($0.name)
    }
    .navigationTitle("Oceans")
    .toolbar { EditButton() }
  }
  Text("\(multiSelection.count) selections")
}

리프레시

  • 가장 일반적인 드래그를 통해 리스트를 아래로 당기는 리프레시 구현
  • 리프레시는 refreshable(action:)을 사용
  • 클로저 안에 들어가는 값은 async형 (async - await)
public func refreshable(
  action: @escaping @Sendable () async -> Void
) -> some View
  • .refreshable {}을 추가하기만 해도 아래로 당겼을 때 로딩화면이 표출

  • async 메소드를 넣으면 되므로, 메소드 하나 추가
    • async 메소드이고, 중간에 await가 있어서, 3초동안 블락되었다가 다음 isOn.toggle()을 실행하는 코드
func getSomeData() async {
  await Task.sleep(3_000_000_000) // 3seconds
  isOn.toggle()
}
  • 사용하는쪽에서는 await getSomeData()로 호출
@State private var isOn = false

NavigationView {
  VStack {
    List(oceans, selection: $multiSelection) {
      Text($0.name)
    }
    .navigationTitle("Oceans")
    .refreshable {
      await getSomeData() // <-
    }
    Toggle("Toggle", isOn: $isOn)
  }
}

refresh

Section

Section형태의 List (Header가 있는 List)

  • 예제에 사용할 Section 모델 준비
    • Section을 차지하는 하나의 값과, 그 하나의 값과 연관된 배열들이 들어있는 형태
    • 아래처럼 Model이 하나 있고, 이 Model들을 모아놓은것이 Section 형태
Model(
  sectionName: "sectionName", 
  contents: [
    Item(:), 
    Item(:)
  ]
)

let sectionItems = [Model]()
  • 아래처럼 구현
struct Sea: Hashable, Identifiable {
    let name: String
    let id = UUID()
}

struct OceanRegion: Identifiable {
    let name: String
    let seas: [Sea]
    let id = UUID()
}


private let oceanRegions: [OceanRegion] = [
  OceanRegion(name: "Pacific",
              seas: [Sea(name: "Australasian Mediterranean"),
                     Sea(name: "Philippine"),
                     Sea(name: "Coral"),
                     Sea(name: "South China")]),
  OceanRegion(name: "Atlantic",
              seas: [Sea(name: "American Mediterranean"),
                     Sea(name: "Sargasso"),
                     Sea(name: "Caribbean")]),
  OceanRegion(name: "Indian",
              seas: [Sea(name: "Bay of Bengal")]),
  OceanRegion(name: "Southern",
              seas: [Sea(name: "Weddell")]),
  OceanRegion(name: "Arctic",
              seas: [Sea(name: "Greenland")])
]
  • 사용하는쪽에서는 oceanRegions의 ForEach문을 돌면서 content에는 seas배열을 사용하고, header에는 name을 사용
  NavigationView {
    List(selection: $singleSelection) {
      ForEach(oceanRegions) { region in
        Section(
          content: {
            ForEach(region.seas) { sea in
              Text(sea.name)
            }
          },
          header: {
            Text("Header \(region.name)")
          }
        )
      }
    }
  }

Hierarchical List

  • 마치 컴퓨터에서 폴더를 열듯이 계층구조 UI구현이 가능

  • 아래 생성자를 이용
    • children이라는 파라미터에, 첫번째 파라미터로 들어간 data의 프로퍼티에 자기 자신 형태의 Model이 배열로 들어간 프로퍼티를 넣어서 사용
public init<Data, RowContent>(
  _ data: Data,
  children: KeyPath<Data.Element, Data?>,
  @ViewBuilder rowContent: @escaping (Data.Element) -> RowContent
)
  • 모델 정의
    • 모델에는 당연히 자기 자신의 형 배열 프로퍼티를 갖고 있게 구현
struct FileItem: Hashable, Identifiable, CustomStringConvertible {
  var id: Self { self }
  var name: String
  var children: [FileItem]? = nil // <- 자기 자신 형태를 배열로 갖고 있는 것
  var description: String {
    switch children {
    case nil:
      return "📄 \(name)"
    case .some(let children):
      return children.isEmpty ? "📂 \(name)" : "📁 \(name)"
    }
  }
}
  • 예제 데이터 정의
let fileHierarchyData: [FileItem] = [
  FileItem(name: "users", children:
            [FileItem(name: "user1234", children:
                        [FileItem(name: "Photos", children:
                                    [FileItem(name: "photo001.jpg"),
                                     FileItem(name: "photo002.jpg")]),
                         FileItem(name: "Movies", children:
                                    [FileItem(name: "movie001.mp4")]),
                         FileItem(name: "Documents", children: [])
                        ]),
             FileItem(name: "newuser", children:
                        [FileItem(name: "Documents", children: [])
                        ])
            ]),
  FileItem(name: "private", children: nil)
]
  • 사용하는 곳에서 List 생성자 중 children 파라미터가 있는 생성자 사용
List(fileHierarchyData, children: \.children) { item in
  Text(item.description)
}

* 전체 코드: https://github.com/JK0369/ExList-SwiftUI

* 참고

https://developer.apple.com/documentation/swiftui/list

Comments