-Preview-

Selected Profile Image

-Project progress-

  • SplashView 구현
  • CustomTabView 구현
  • SettingView 구현
  • HomeView 구현
  • AccountView 구현
  • SwiftDataView 구현
  • DailyQuizView 구현 / 퀴즈 DB, 랜덤 퀴즈 구현 필요
  • RankSystem 구현

-View made today-

AccountViewSelect profile image
프로필 이미지를 클릭하여 사진 선택기능 구현PhotosPicker로 사용자 갤러리에 접근

-Code Review-

Select profile image

사용자가 자유롭게 프로필 이미지를 꾸밀 수 있도록 사용자의 갤러리에서 사진을 불러오고, 이를 프로필 사진에 적용시키는 기능을 구현하였다.
이 때 사용자의 갤러리를 불러오기 위해 PhotosPicker 기능을 활용하였고, 사용자의 정보를 저장하기 위해 UserDefaults를 사용하였다.

핵심코드

PhotosPicker(selection: $selectedImage, matching: .images) {
	if let selectedImageData, let image = UIImage(data: selectedImageData) {
		Image(uiImage: image)
			.resizable()
			.scaledToFill()
			.frame(width: 100, height: 100)
			.clipShape(Circle())
	}
}

1. PhotosPicker란?

PhotosPicker는 앱 사용자의 갤러리에 접근할 수 있도록 도와주는 코드로, iOS 16 이후부터 지원을 시작한 기능이다.

// PhotosPicker의 형태
@MainActor @preconcurrency
struct PhotosPicker<Label> where Label : View
import SwiftUI
import PhotosUI

// PhotosPicker이 기본적인 선언 방법
struct PhotosSelector: View {
    @State var selectedItems: [PhotosPickerItem] = []


    var body: some View {
        PhotosPicker(selection: $selectedItems,
                     matching: .images) {
            Text("Select Multiple Photos")
        }
    }
}
// PhotosPicker에서 필터를 사용할 경우
PhotosPicker(selection: $selectedItems,
             matching: .any(of: [.images, .not(.screenshots)])) {
    Text("Select Photos")
}

PhotosPicker에서는 사용자의 편의를 위해 matching이라는 프로퍼티를 통해 스크린샷은 사용 못하게 하는 조건이나 비디오는 금지하는 등 자유롭게 필터를 걸 수 있다.

2. PhotoPicker 호출하기

먼저 사용자의 갤러리에서 사진을 선택할 수 있도록 도와주는 PhotosPicker를 사용하기 위한 코드를 입력한다.

import PhotosUI

그리고 PhotosPicker의 바인딩 값으로 사용하기 위해 PhotosPickerItem의 값을 가지는 변수를 선언해준다.

// 사용자가 이미지를 선택하기 전까지 해당 변수는 값이 있을 수도, 없을 수도 있기 때문에 옵셔널 값으로 지정
@State private var selectedImage: PhotoPickerItem?

그리고 뷰 내부에 PhotosPicker를 호출해준다.
호출코드는 무척 간단하다.

// selection - PhotosPicker의 바인딩값, 이미지를 저장하는 아이템 지정
// matching - 불러올 이미지의 타입 설정, 불러올 수 있는 형식을 제한하는 것도 가능
PhotosPicker (
	selection: $selectedImage, 
    matching: .images
    ) {
    	// label
    	ZStack {
			Circle()
				.frame(width: 100)
				.foregroundStyle(Color.secondary.opacity(0.5))
             
            // selectedImageData는 PhotosPickerItem의 정보를 저장한 변수
			if let selectedImageData, let image = UIImage(data: selectedImageData) {
				Image(uiImage: image)
					.resizable()
					.scaledToFill()
					.frame(width: 100, height: 100)
					.clipShape(Circle())
			}
		}
	}

3. PhotosPicker 데이터 불러오기

PhotosPicker를 선언하면, 내부에 작성한 label을 통해 사용자의 갤러리에 접근하고 사진을 선택할 수 있게 된다. 그러나 선택된 데이터는 PhotosPickerItem에 저장되기 때문에 화면에 표시되지 않는다.
그렇다면 어떻게 PhotosPickerItem에 있는 사진을 불러올 수 있을까?
답은 .onChangeTask를 이용하는 것이다.

// PhotosPickerItem의 값을 저장할 Data타입의 변수 선언
@State private var selectedImageData: Data?
PhotosPicker (
	selection: $selectedImage, 
    matching: .images) { // label }
    // onChange - 속성 값의 변화를 감지하여 내부 코드를 반환
    // Task - 비동기작업을 수행하기 위한 코드
	.onChange(of: selectedImage) { _, image in
		Task {
        	// loadTransferable - PhotosPickerItem 값을 Data 값으로 변환하기 위한 코드
			if let data = try? await image?.loadTransferable(type: Data.self) {
				selectedImageData = data
                // 변환된 Data 타입의 코드를 UserDefaults를 통해 저장한다.
				UserDefaults.standard.set(data, forKey: "profileImage")
			}
		}
	}

위의 코드를 통해 selectedImage라는 PhotosPickerItem타입을 가진 변수의 값에 변화가 일어났을 때 내부의 Task코드가 작동한다. 이를 통해 PhotosPickerItem의 값을 Data타입으로 변환시켜 저장한다.

// selectedImageData의 값이 nil이 아닐 때 작동
if let selectedImageData, let image = UIImage(data: selectedImageData) {
	// 뷰에 보여질 이미지를 UIImage: data 타입으로 한다.
    // UIImage의 data 값에 selectedImageData값을 가진 image 값을 적용한다.
	Image(uiImage: image)
		.resizable()
		.scaledToFill()
		.frame(width: 100, height: 100)
		.clipShape(Circle())
			}

저장된 Data값을 이용하여 PhotosPickerlabelImage(uiImage: data)를 선언하여 data에는 PhotosPickerItem의 데이터를 저장한 값을 넣어 화면에 사용자가 선택한 이미지가 보이도록 한다.

4. UserDefaults를 통한 값 저장

마지막으로 사용자가 저장한 사진이 사용자의 로컬저장소를 통해 저장되도록 UserDefaults를 사용하여 값을 저장하고 불러오도록 한다.

// 메인뷰에서 프로필사진의 존재 유무를 판단하고 값이 nil일 경우 기본값이 되는 값 입력
.onAppear() {
	if UserDefaults.standard.data(forKey: "profileImage") == nil {
		let profileImage = UIImage(named: "profile_image")
		let imageData = profileImage?.pngData()
                
		UserDefaults.standard.set(imageData, forKey: "profileImage")
	}
}
// PhotosPicker를 사용한 뷰에서 사용자가 지정한 사진을 불러오도록 설정
// 뷰를 열 때마다 UserDefaults의 값을 가져와 프로필 이미지 설정
.onAppear() {
	self.selectedImageData = UserDefaults.standard.data(forKey: "profileImage")
// MainView에서도 프로필 사진을 볼 수 있기 때문에 MainView의 값도 변경
// UserDefaults값을 가져오는 변수를 선언
// Image를 통해 프로필 사진 불러오기
private let userProfileImage = UserDefaults.standard.data(forKey: "profileImage")

Image(uiImage: UIImage(data: userProfileImage!)!)
	.resizable()
	.scaledToFill()
	.frame(width: 50, height: 50)
	.clipShape(Circle())
	.offset(y: -25)
    // 이미지를 클릭하면 Account뷰로 이동
	.onTapGesture {
		self.tabIndex = .account
	}

5. 구현 결과물


-Today's lesson review-

오늘은 사용자가 프로필사진을 선택할 수 있도록 하기 위해 PhotosPicker를 활용한 코드를 처음으로 작성해보았다.
간단할 것이라고 예상했던 것과 다르게 초반에 시행착오를 많이 겪었다.
특히 onChange와 Task를 활용하는 것이나, PhotosPicker의 loadTransferable을 사용하는 것이 너무 어려웠다.
이번에 구현한 것도 주먹구구식으로 완성한 것과 다름이 없어서 무척 아쉽다.
기회가 되면 추후 다시 PhotosPicker를 공부하여 더 완벽하게 다룰 수 있도록 하고싶다.
profile
이유있는 코드를 쓰자!!

0개의 댓글