[SwiftUI] FavoriteButton 조작하기

원문: 튜토리얼 페이지

Handling User Input

앞 포스팅까지는 json에 저장된 isFavorite 값만 보고 즐겨찾기 유무를 판단했지만, 만약 유저가 직접 즐겨찾기를 설정하게 된다면 유저가 설정한 값을 저장할 수 있어야 한다.

ModelData를 observable object 에 저장해 관리한다. SwiftUI는 observable object들의 변경 사항을 확인하고 변경이 있을 경우 View를 갱신할 수 있도록 한다.

Use an Observable Object for Storage

  • ModelData 파일에 Combine import 및 ModelData가 ObservableObject 채택
import Combine

final class ModelData: ObservableObject {
    @Published var landmarks: [Landmark] = load("landmarkData.json")
}
  • ObservableObject를 채택한 후 observing 될 데이터인 landmarks를 넣는다.

SwiftUI가 ModelData를 계속 체크하려면 ObservableObject를 채택해서 내 데이터를 확인해달라고 표현해두어야 한다. 그래야 추후에 데이터가 바뀔 때 View 갱신이 이뤄진다. 체크될 데이터는 @Published를 앞에 붙여준다. 그래야 데이터가 변경됐는지 확인할 수 있다.

Adopt the Model Object in Your Views

  • LandmarkList에 modalData 추가

LandmarkList에 @EnvironmentObject var modelData: ModelData 프로퍼티를 추가한다. Preview에도 environmentObject를 추가한다.

image

modelData 부모에 envirenmentObject(_:)이 적용된 경우 자동으로 값을 가져온다.

  • 아까 ModelData 파일에서 landmarks를 final class 내부로 집어넣어서 그런지 에러가 난다. 에러가 나는 부분을 modelData.landmarks로 접근하도록 수정한다.

  • LandmarkRow와 LandmarkDetail의 Preview에서도 에러가 나기 때문에 ModelData를 생성해서 접근하도록 수정한다.

  • ContentView의 preview에 .environmentObject(ModelData())를 추가한다. (object가 subview들에서도 사용가능해진다?)

Subview에서 object를 사용해야하는 일이 있는데, 상위뷰에서 environmentObject(_:) 를 추가하지 않는 경우 Preview를 확인할 수 없다.

  • 시뮬레이터와 디바이스에서 앱을 실행할 때 model object를 사용할 수 있도록 LandmarksApp의 body에 environmentObject(_:)를 추가한다.

image

modelData를 선언할 때 @StateObject 를 사용했다. 이 속성을 사용하면 app life time 동안 한 번만 초기화 된다. 요 속성은 현재 사용한 App instance 뿐만 아니라 view에서 사용할 때도 동일하게 적용된다.

Create a Favorite Button for Each Landmark

즐겨찾기한 랜드마크를 토글을 통해 필터링 할 수 있지만 아직 사용자가 직접 추가하거나 제거할 수는 없다. 이번 Section에서는 디테일 뷰에서 직접 즐겨찾기를 추가하거나 제거할 수 있도록 구현한다.

FavoriteButton.swift 파일 생성 및 코드 추가

image

  • Button 파일에 즐겨찾기 버튼을 눌렀는지 안눌렀는지를 판단할 isSet 프로퍼티를 추가한다. @Binding 속성을 사용하기 때문에 view에서 변경된 내용은 data source로 다시 전달된다.

  • View에 Button을 추가한다.

var body: some View {
        Button {
            isSet.toggle()
        } label: {
            Label("Toggle Favorite", systemImage: isSet ? "star.fill" : "star")
                .labelStyle(.iconOnly)
                .foregroundColor(isSet ? .yellow : .gray)
        }
    }

Button의 Label title은 .iconOnly 속성때문에 뷰에 나타나지는 않지만 VoiceOver에서 사용할 때 accessibility가 향상되므로 추가한다.

LandmarkDetail에 버튼 추가

  • modelData에 접근할 수 있도록 modelData를 추가하고 Preview에서도 다른 뷰와 마찬가지로 environmentObject(_:)를 사용한다.
  • landmark 이름 옆에 버튼이 나오도록 코드를 수정한다.
HStack {
                    // landmark의 name을 노출
                    Text(landmark.name)
                        .font(.title)
                    FavoriteButton(isSet: $modelData.landmarks[landmarkIndex].isFavorite)
                }

isSet이 바인딩 속성이기 때문에 $modelData.landmarks로 접근한다.

결과 확인

번외

  • labelStyle(.titleAndIcon)

마무리

튜토리얼에서 나오는 @State, @Binding, @EnvironmentObject와 같은 생소한 친구들에 대해 더 공부해야 SwiftUI를 자유롭게 다뤄볼 수 있을 것 같다. 공부는 언제 끝나는가… 😭

Leave a comment