clamp
Clamp
clamp
글쓰기 관리
전체 방문자
오늘
어제
  • 분류 전체보기 (509)
    • IOS (85)
    • SwiftUI+TCA+Combine (9)
    • RxSwift + MVVM (56)
    • Clean Architecture (12)
    • SWIFT (56)
    • iOS - TDD (2)
    • 디자인패턴 (4)
    • CS (56)
      • 알고리즘 (29)
      • 운영체제 (15)
      • 자료구조 (2)
      • 네트워킹 (4)
      • 기타 (6)
    • 회고 (0)
    • Firebase (18)
    • SwiftUI (10)
    • iOS - UIKit (11)
    • iOS - 오픈소스 (6)
    • 코딩테스트 (166)
      • 프로그래머스 (164)
    • 정보처리기사 (14)
    • GitHub (2)
글쓰기 / 관리자

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • uikit
  • Q
  • Swift
  • ㅅ

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
clamp

Clamp

[SwiftUI] Tutorials3. Handling User Input
SwiftUI

[SwiftUI] Tutorials3. Handling User Input

2022. 12. 11. 17:27

Landmarks앱에서 사용자는 즐겨찾는 장소를 즐겨찾기하고, 즐겨찾기만 표시하도록 목록을 필터링할 수 있다. 이 기능을 만들려면 먼저 스위치를 목록에 추가하여 사용자가 즐겨찾기 한 항목들만 볼 수 있게 한 다음 사용자가 탭 하여 랜드마크를 즐겨찾기로 표시하는 별 모양의 버튼을 추가한다.

 

즐겨찾기 표시하기

사용자에게 즐겨찾기를 한눈에 보여줄 수 있도록 목록을 향상시키는 것 부터 시작한다. 각 LandmarkRow에 즐겨찾는 랜드마크를 표시하는 별표를 추가한다.

 

LandmarkRow.swift파일을 수정한다. 현재 랜드마크가 즐겨찾기 항목인지 확인하기 위해 spacer뒤 if문 안에 별 이미지를 추가한다.

SwiftUI는 if문을 사용하여 조건부로 뷰를 포함시킨다.

struct LandmarkRow: View {
    
    var landmark: Landmark
    
    var body: some View {
        HStack{
            landmark.image
                .resizable()
                .frame(width: 50, height: 50)
            Text(landmark.name)
            
            Spacer()
            
            if landmark.isFavorite{
                Image(systemName: "star.fill")
                    .imageScale(.medium)
            }
        }
    }
}

landmark의 isFavorite에 대한 정보는 JSON파일을 확인하면 알 수 있다.

 

시스템 이미지가 기본적으로 벡터 이미지이기 때문에 컬러를 foregroundColor()수정자로 변경할 수 있다.

별(이미지)은 랜드마크가 가지고있는 isFavorite프로퍼티가 true일 때에만 나타난다.

 

2. 리스트 뷰 필터링

리스트 뷰를 커스터마이징 할 수 있다. 모든 랜드마크를 보여주거나 선택한 랜드마크만을 보여줄 수 있다. 이것을 해보기 위해 LandmarkList에 약간의 state를 추가해야 한다.

 

LandmarkList.swift파일을 선택하여 showFavoriteOnly라 불리는 @State프로퍼티를 LandmarkList에 추가한다. 초기값으로 false를 선언한다.

 

랜드마크 리스트를 showFavoriteOnly 프로퍼티를 체크하는 것과 landmark.isFavorite값을 체크하여 필터링 한다.

 

struct LandmarkList: View {
    @State var showFavoritesOnly = false
    
    var body: some View {
        NavigationView{
            List(landmarks){ landmark in
                if !self.showFavoritesOnly || landmark.isFavorite{
                    NavigationLink(destination: LandmarkDetail(landmark: landmark)){
                        LandmarkRow(landmark: landmark)
                    }
                }
            }
            .navigationTitle("Landmarks")
        }
    }
}

 

State를 토클(on - off)하기 위한 "컨트롤"을 추가한다.

유저에게 리스트의 필터에 대한 권한을 주기 위해 showFavoriteOnly의 값을 바꿀 수 있는 버튼을 추가해야 한다.

바인딩(Binding)을 토글 컨트롤에 전달하여 이 작업을 수행한다.

'Binding'은 변경 가능한 상태에 대한 참조로서의 역할을 한다. 유저가 토글을 해서 on > off 혹은 off > on 할 때 컨트롤은 바인딩을 사용하여 그에 맞춰 뷰의 상태를 업데이트 한다.

 

중첩된 ForEach그룹을 만들어 랜드마크를 행으로 변형시킨다.

*리스트 내에 정적 뷰와 동적 뷰를 결합하기 위해 혹은 2개 이상의 서로 다른 그룹의 동적 뷰를 결합하기 위해

ForEach타입을 데이터 컬렉션을 List로 전달하는 것 대신 사용한다.

 

showFavoriteOnly가 true일 때 Landmarks에서 isFavorite이 true인 것만을 필터링 하여 보여줄 리스트도 만들어준다.

import SwiftUI
import MapKit

struct LandmarkList: View {
    @State var showFavoritesOnly = false
    
    var filteredLandmarks: [Landmark]{
        landmarks.filter{ landmark in
            (!showFavoritesOnly || landmark.isFavorite)
        }
    }
    
    var body: some View {
        NavigationView{
            List{
                Toggle(isOn: $showFavoritesOnly){
                    Text("Favorites only")
                }
                ForEach(filteredLandmarks){landmark in
                    NavigationLink{
                        LandmarkDetail(landmark: landmark)
                    }label: {
                        LandmarkRow(landmark: landmark)
                    }
                }
                .navigationTitle("Landmarks")
            }
        }
    }
}
struct LandmarkList_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkList()
    }
}

 

Observable객체 저장하기 

사용자가 즐겨찾는 특정 랜드마크를 제어할 수 있도록 랜드 마크 데이터를 observable객체에 저장한다.

observable객체는 SwiftUI환경의 스토리지에서 뷰에 바인딩 될 수 있는 데이터의 사용자 정의 객체이다. SwiftUI는 뷰에 영향을 줄 수 있는 observable객체에 대한 변경 사항을 감시하고 변경 후 뷰의 올바른 버전을 표시한다.

 

ModelData.swift파일에서 Combine프레임워크를 import하고 ObservableObject 프로토콜을 따르는 새 Model을 선언한다. SwiftUI는 observable객체를 구독하고 데이터가 변경될 때 새로 고쳐야 하는 모든 뷰를 업데이트한다.

landmarks배열을 Model로 이동한다.

 

Observable객체는 subscribers가 변경 사항을 알 수 있도록 데이터 변경 사항을 게시해야 한다.

landmarks배열에 @Published특성을 추가한다.

@Published는  SwiftUI에서 가장 유용한 속성중 하나이며 변경이 발생할 때 자동으로 알리는 관찰 가능한 observable object를 만들 수 있다. SwiftUI는 이런 변경 사항을 자동으로 모니터링하고 데이터에 의존하는 모든 View의 body속성을 다시 호출한다.

import Foundation
import Combine

final class ModelData: ObservableObject{
   @Published var landmarks: [Landmark] = load("landmarkData.json")

}

func load<T: Decodable>(_ filename: String) -> T{
    let data: Data
    
    guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
    else {
        fatalError("Couldn't find \(filename) in main bundle")
    }
    
    do{
        data = try Data(contentsOf: file)
    }catch let error{
        fatalError("Couldn't load \(filename) from main bundle: \n\(error)")
    }
    
    do{
        let decoder = JSONDecoder()
        return try decoder.decode(T.self, from: data)
    } catch let error{
        fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
    }
}

 

뷰에서 모델 객체 채택하기

개체를 만들었 으므로 View를 업데이트 하여 앱의 데이터 저장소로 채택해야 한다.

LandmarkList.swift에서 @EnvirionmentObject ModelData변수를 추가하고 preview에 수정자를 추가한다.

environmentObject 수정자가 부모에 적용되는 동안 ModelData프로퍼티는 해당 값을 자동으로 가져온다.

랜드마크 필터링 시 데이터로 사용하도록 수정하고  LandmarkDetail에서 Envirionment object에 대해 작업하도록 preview를 수정한다.

import SwiftUI
import MapKit

struct LandmarkList: View {
    @Environment var modelData: ModelData
    @State var showFavoritesOnly = false
    
    var filteredLandmarks: [Landmark]{
        modelData.landmarks.filter{ landmark in
            (!showFavoritesOnly || landmark.isFavorite)
        }
    }
    
    var body: some View {
        NavigationView{
            List{
                Toggle(isOn: $showFavoritesOnly){
                    Text("Favorites only")
                }
                ForEach(filteredLandmarks){landmark in
                    NavigationLink{
                        LandmarkDetail(landmark: landmark)
                    }label: {
                        LandmarkRow(landmark: landmark)
                    }
                }
                .navigationTitle("Landmarks")
            }
        }
    }
}
struct LandmarkList_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkList()
            .environmentObject(ModelData())
    }
}

 

struct LandmarkDetail_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkDetail(landmark: ModelData().landmarks[0])
    }
}
struct LandmarkRow_Previews: PreviewProvider {
    static var landmarks = ModelData().landmarks
    
    static var previews: some View {
        Group{
        LandmarkRow(landmark: landmarks[0])
        LandmarkRow(landmark: landmarks[1])
        }
            .previewLayout(.fixed(width: 300, height: 70))
    }
}

다음으로 시뮬레이터나 기기에서 앱을 실행할 때 환경에 모델개체를 배치하도록 ContentView를 업데이트 한다,

모델 인스턴스를 생성하고 수정자를 사용하여 제공한다.

속성을 사용하여 앱 수명동안 지정된 속성에 대한 모델 개채를 한 번만 초기화할 수 있다. 

import SwiftUI

@main
struct LandmarksApp: App {
    @StateObject private var modelData = ModelData()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(modelData)
        }
    }
}

 

각 랜드마크에 대한 즐겨찾기 버튼 만들기

Landmarks앱은 필터링된 랜드마크 보기와 필터링 되지않은 랜드마크 보기 간에 전환할 수 있지만 즐겨찾는 랜드마크 목록은 여전히 하드코딩 되어있다. 사용자가 즐겨찾기를 추가 및 제거할 수 있도록 하려면 랜드마크 상세보기에 즐겨찾기 버튼을 주가해야 한다.

 

먼저 FavoriteButton.swift라는 파일을 생성한다. 버튼의 현재 상태를 나타내는 바인딩 변수 isSet을 추가하고 바인딩을 사용하기 때문에 이 뷰 내에서 변경 한 내용은 다시 데이터 소스로 전달된다.

import SwiftUI

struct FavoriteButton: View {
    @Binding var isSet: Bool
    var body: some View {
    //버튼
        Button{
        //버튼의 action
            isSet.toggle()
        }label:{
            Label("Toggle Favorite", systemImage: isSet ? "star.fill" : "star")
                .labelStyle(.iconOnly)
                .foregroundColor(isSet ? .yellow : .gray)
        }
    }
}

struct FavoriteButton_Previews: PreviewProvider {
    static var previews: some View {
        FavoriteButton(isSet: .constant(true))
    }
}

Landmark Detail 에서 즐겨찾기 여부를 선택해야하기 때문에 해당 랜드마크의 인덱스를 구한다

 

 

 

랜드마크의 이름과 버튼을 디테일 뷰에 나타내준다

 

struct LandmarkDetail: View {
    @EnvironmentObject var modelData: ModelData
    var landmark: Landmark

    var landmarkIndex: Int {
        modelData.landmarks.firstIndex(where: { $0.id == landmark.id })!
    }

    var body: some View {
        ScrollView {
            MapView(coordinate: landmark.locationCoordinate)
                .ignoresSafeArea(edges: .top)
                .frame(height: 300)

            CircleImage(image: landmark.image)
                .offset(y: -130)
                .padding(.bottom, -130)

            VStack(alignment: .leading) {
                HStack {
                    Text(landmark.name)
                        .font(.title)
                    FavoriteButton(isSet: $modelData.landmarks[landmarkIndex].isFavorite)
                }

                HStack {
                    Text(landmark.park)
                    Spacer()
                    Text(landmark.state)
                }
                .font(.subheadline)
                .foregroundColor(.secondary)

                Divider()

                Text("About \(landmark.name)")
                    .font(.title2)
                Text(landmark.description)
            }
            .padding()
        }
        .navigationTitle(landmark.name)
        .navigationBarTitleDisplayMode(.inline)
    }
}

struct LandmarkDetail_Previews: PreviewProvider {
    static let modelData = ModelData()

    static var previews: some View {
        LandmarkDetail(landmark: modelData.landmarks[0])
            .environmentObject(modelData)
    }
}
저작자표시 비영리 동일조건 (새창열림)
    'SwiftUI' 카테고리의 다른 글
    • [SwiftUI] Tutorials5. Animating View and Transitions
    • [SwiftUI] Tutorials4. Drawing Paths and Shape
    • [SwiftUI] Tutorials 2. Building List and Navigation
    • [SwiftUI] Tutorials 1. Creating and Combining View
    clamp
    clamp
    주니어 iOS개발자의 발악!!!!!!!

    티스토리툴바