본문 바로가기
Apple/SwiftUI

[iOS] Apple의 Introducing SwiftUI 공부하기 3

by 어멘드 2023. 7. 27.
반응형

2편은 여기에서 확인할 수 있다.

 

[iOS] Apple의 Introducing SwiftUI 공부하기 2

1편은 여기에서 확인할 수 있다. [iOS] Apple의 SwiftUI Tutorials 공부하기 1 애플 개발자 문서에서 제공하는 SwiftUI Tutorials를 읽으면서 SwiftUI를 공부해보자. Introducing SwiftUI | Apple Developer Documentation SwiftUI

please-amend.tistory.com

 오늘은 세번째 소챕터 Handling User Input을 공부해보자. 랜드마크 즐겨찾기 기능을 구현한다.

1. SwiftUI Essentials
    a. Creating and Combining Views
    b. Building Lists and Navigation
    c. Handling User Input
2. Drawing and Animation
    a. Drawing Paths and Shapes
    b. Animating Views and Transitions
3. App Design and Layout
    a. Composing Complex Interfaces
    b. Working with UI Controls
4. Framework Integration
    a. Interfacing with UIKit
    b. Creating a watchOS App
    c. Creating a macOS App

 

Section 1

랜드마크 즐겨찾기 기능을 구현해보자.

Landmark 구조체에 Bool 타입의 isFavorite 프로퍼티를 추가한다. landmarkData.json 파일을 살펴보면 각 랜드마크마다 isFavorite 값을 가지고 있다. 따라서 새로운 프로퍼티를 추가하여도 디코딩에는 문제가 없다.

LandmarkRow.swift 파일로 이동하여 isFavorite 값에 따라 별 아이콘이 보이는 기능을 구현하자. Spacer 다음에 if 문을 사용하여 isFavorite == true인 경우에만 별 이미지를 보여주는 코드를 추가한다. SwiftUI 블럭 안에서는 조건에 따라 뷰를 보여주거나 숨길 때 if 문을 사용할 수 있다.

별 아이콘의 색상을 바꿔보자. foregroundColor(_:) modifier를 사용한다.

 

Section 2

이제 모든 목록보기 <-> 즐겨찾기만 보기로 전환할 수 있는 기능을 구현해보자. 이렇게 하려면 LandmarkList에 State를 추가해야 한다. State는 시간이 지남에 따라 바뀔 수 있는 값 또는 값 집합이며, 뷰의 동작, 콘텐츠 또는 레이아웃에 영향을 미친다. State값이 변경되면, SwiftUI는 State 값에 의존하는 뷰들을 업데이트한다.

LandmarkList.swfit로 이동하여 여러 디바이스를 설정했던 코드를 제거하고 프리뷰에서 단 하나의 LandmarkList만 보이도록 수정한다.

그리고 showFavoritesOnly라는 @State 프로퍼티를 추가한다. 초기값은 false로 설정해준다. @State 프로퍼티는 특정 뷰나 서브뷰에 대한 특정한 정보를 담기 때문에 항상 private으로 생성한다. 서브뷰와 값을 공유하고 싶다면, 바인딩을 통해 공유하도록 한다.

이제 showFavoritesOnly 값에 따라 리스트에 표시할 랜드마크들을 필터링한 목록을 만들자. filteredLandmarks라는 [Landmark] 타입의 연산 프로퍼티를 추가한다. showFavoriteOnly == false인 경우 모든 랜드마크가 포함되고, 반대의 경우 isFavorite인 랜드마크만 포함되도록 filter의 클로저를 작성해준다.

그리고 List의 데이터를 landmarks에서 filteredLandmarks로 수정한다. 디버깅을 위해 showFavoritesOnly = true로 변경하고 캔버스를 확인해보면, 이렇게 별표시가 된 즐겨찾기 랜드마크들만 리스트에 표시된 것을 볼 수 있다.

 

Section 3

사용자가 showFavoriteOnly 값을 변경할 수 있도록 토글 버튼을 추가하자. 여기에는 바인딩을 사용한다. 바인딩은 가변 상태에 대한 참조역할을 한다.

기존 List를 List에 임베드하여 중첩되게 만들고, 내부 List는 ForEach 타입으로 변경한다. List내에서 정적 뷰와 동적 뷰를 결합하거나 두 개 이상의 다른 동적 뷰를 결합하려면 List 대신 ForEach 유형을 사용한다. ForEach는 데이터 배열을 뷰 배열로 만들어주는 단순한 map 함수 역할이고, List는 UIKit의 UITableView와 같은 '뷰'이다. 따라서 List 안에 들어가는 여러 LandmarkRow 뷰들을 ForEach로 만들어 넣어주는 것이다.

이제 List의 첫 자식뷰로 토글버튼을 추가한다. $을 사용하면 State 변수에 대한 바인딩에 접근할 수 있다. 라이브 모드로 변경하여 토글버튼을 터치해보면 토글 값에 따라 목록이 잘 필터링되는 것을 볼 수 있다.

 

Section 4

사용자가 랜드마크를 즐겨찾기로 설정할 수 있도록 하려면 랜드마크 데이터를 옵저버블한 개체로 저장해야 한다. ObservableObject는 객체가 변경되기 전에 방출하는 퍼블리셔가 있는 객체 유형이다. 즉 변경에 대해 관찰 가능한 객체이다. ObservableObject는 뷰에 바인딩될 수 있다. SwiftUI는 뷰에 영향을 미칠 수 있는 ObservableObject의 변경 사항을 감시하고, 변경 시 뷰를 업데이트한다.

ModelData.swift로 이동한다. Combine 프레임워크를 임포트하고, ObservableObject 프로토콜을 준수하는 ModelData라는 새로운 클래스를 하나 선언한다. 그리고 landmarks를 ModelData 내부로 옮기고 앞에 @Published 속성을 붙여준다. 기본적으로 ObservableObject는 @Published 속성이 변경되기 전에 변경된 값을 방출한다.

 

Section 5

새로운 모델 ModelData를 적용하자.

LandmarkList.swift로 이동한다. modelData를 @EnvironmentObject 프로퍼티로 추가한다. @EnvironmentObject는 부모/조상뷰에서 정의한 ObservableObject를 준수하는 프로퍼티에 대한 프로퍼티 래퍼로, 자식/손자뷰에서 접근 가능한 프로퍼티이다. 전역적인 느낌으로 뷰 계층을 뛰어넘어 데이터를 공유할 때 사용 가능하다. 프로퍼티를 @EnvironmentObject로 선언하는 경우, 상위뷰에서 environmentObject(_:) 모디파이어를 호출하여 해당 모델 객체를 주입해야 한다. landmarks를 사용하던 부분을 modelData.landmarks로 수정한다. 프리뷰 부분도 environmentObject(_:) modifier를 사용하여 수정해준다.

LandmarkDetail과 LandmarkRow, ContentView도 ModelData에 맞게 수정해준다.

LandmarksApp으로 이동하여 @StateObject 프로퍼티로 modelData를 추가한다. App에서 @StateObject 속성을 사용하여 앱 수명 동안 한 번만 주어지는 속성에 대한 모델 객체를 초기화할 수 있다. 그리고 ContentView에 environmentObject(_:)  로 modelData를 넘겨준다.

 

Section 6

랜드마크 목록은 아직 하드코딩 되어있다. 사용자가 직접 즐겨찾기 등록/해제를 할 수 있도록 버튼을 추가하자.

FavoriteButton.swift 파일을 생성한다. 버튼의 현재 상태를 나타내는 isSet @Binding 프로퍼티를 추가하고 프리뷰에는 대한 상수 값을 전달한다. @Binding을 사용하여 데이터를 저장하는 프로퍼티와 데이터를 표시하고 변경하는 뷰 사이에 양방향 연결을 만들 수 있다.

Button을 생성한다. 터치 시 isSet 값을 토글하고, isSet 값에 따라 모습이 바뀌도록 해준다.

LandmarkDetail 뷰에 FavoriteButton을 추가해서 버튼의 isSet 프로퍼티를 랜드마크의 isFavorite 속성에 바인딩하자. 현재 디테일뷰에 표시된 랜드마크의 인덱스를 계산하는 연산 프로퍼티 landmarkIndex를 추가한다. 인덱스를 계산하기 위해서는 전체 랜드마크 목록이 필요하므로 @EnvironmentObject를 사용하여 부모뷰로부터 modelData를 받자.

랜드마크 이름을 표시하던 Text를 HStack에 임베드하고 FavoriteButton을 추가한다. $를 사용하여 현재 랜드마크의 isFavorite 프로퍼티에 대한 바인딩을 전달한다. 이렇게 하면 버튼을 통해 모델의 isFavorite 값을 변경시킬 수 있다. @Binding을 사용했기 때문에 FavoriteButton의 isSet 프로퍼티의 변경 사항은 원본 데이터 소스인 landmark로 다시 전달된다.  LandmarksApp에 선언한 @StateObject modelData라는 단일 모델을 모든 하위 뷰들이 공유하고 있기 때문에 화면을 이동하더라도 문제없다. LandmarkList로 이동하여 라이브 프리뷰 기능을 통해 기능이 잘 작동되는지 확인 가능하다.

 

반응형

댓글