[ swift ] UIMenu와 UserDefaults를 활용한 온도 단위 설정 기능 구현하기

sonny·2025년 1월 11일
2

TIL

목록 보기
99/140

오늘은 UIMenuUserDefaults를 이용해 온도 단위(섭씨/화씨) 설정 기능을 구현한 과정을 기록하려한다.

이 기능은 사용자 경험을 개선하기 위해 사용자가 선택한 온도 단위를 앱 전반에 동기화하고 유지하도록 구현을 했다.


구현 목표는

아래 사진처럼 아이폰의 날씨 앱에서 설정버튼을 만들기 위함인데,

UIMenu를 활용해 설정 버튼을 누르면 온도 단위를 선택할 수 있는 메뉴를 표시한 뒤,

선택한 온도 단위를 UserDefaults에 저장해서 앱을 종료하고 다시 실행해도 설정이 유지되도록 하는 것.

그리고 선택한 단위에 따라 메뉴의 체크 표시가 즉시 업데이트되도록 구현하고 향후에 화면 전반의 온도 표기 변경을 위해 API와 컨트롤러 사이에 바인딩 로직을 추가할 계획이다.


UIMenu로 온도 단위 선택 메뉴 생성

UIMenu를 사용하면 iOS에서 버튼을 클릭했을 때 옵션 메뉴를 간편하게 생성할 수 있었다.

https://ios-development.tistory.com/735 해당 블로그에서 어떤식으로 사용이 가능한지 파악을 해보고

온도 단위(섭씨/화씨)를 선택하는 메뉴만 간단하게 구현해보기 위해 MenuHelper라는 헬퍼 구조체를 작성했다.

코드 구현

import UIKit

struct MenuHelper {
    static func createSettingsMenu() -> UIMenu {
        let celsiusAction = UIAction(
            title: "섭씨",
            image: UIImage(systemName: "degreesign.celsius"),
            state: SettingsManager.getTempUnit() == .celsius ? .on : .off
        ) { _ in
            SettingsManager.saveTempUnit(.celsius)
            NotificationCenter.default.post(name: .tempUnitChanged, object: nil)
        }
        
        let fahrenheitAction = UIAction(
            title: "화씨",
            image: UIImage(systemName: "degreesign.fahrenheit"),
            state: SettingsManager.getTempUnit() == .fahrenheit ? .on : .off
        ) { _ in
            SettingsManager.saveTempUnit(.fahrenheit)
            NotificationCenter.default.post(name: .tempUnitChanged, object: nil)
        }
        
        return UIMenu(
            title: "온도 설정",
            options: .displayInline,
            children: [celsiusAction, fahrenheitAction]
        )
    }
}

UIAction
각각 섭씨와 화씨 선택 항목을 생성하고 선택된 상태에 따라 .on 또는 .off로 설정해서 체크 표시를 관리했다. 삼항 연산자로 간단하게 구현할 수 있었다.

NotificationCenter
선택한 값이 저장된 후에 NotificationCenter를 통해서 변경 사항을 앱 전반에 전파해준다.

UIMenu
섭씨와 화씨 메뉴 항목을 포함한 메뉴를 생성했다.


선택 값 저장을 위한 UserDefaultsSettingsManager

온도 단위 설정은 팀원들과의 회의에서 UserDefaults를 이용해 저장하기로 했다.

유저 디폴츠로 간단하게 구현해도 앱을 재시작하게 될 때 사용자의 선택 값이 유지되니 굳이 코어데이터를 사용하지 않아도 될 것 같아서 코어데이터보다는 덜 머리아프게 구현했던 것 같다.

그리고 저장과 불러오는 작업은 SettingsManager라는 구조체에서 관리하도록 구현했다.

코드 구현

import Foundation

enum TempUnit: String {
    case celsius = "섭씨"
    case fahrenheit = "화씨"
}

struct SettingsManager {
    private static let tempUnitKey = "TemperatureUnit"
    
    static func saveTempUnit(_ unit: TempUnit) {
        UserDefaults.standard.set(unit.rawValue, forKey: tempUnitKey)
    }
    
    static func getTempUnit() -> TempUnit {
        let value = UserDefaults.standard.string(forKey: tempUnitKey) ?? TempUnit.celsius.rawValue
        return TempUnit(rawValue: value) ?? .celsius
    }
}

saveTempUnit
선택한 단위를 UserDefaults에 저장한다.

getTempUnit
그리고 UserDefaults에서 저장된 값을 불러오고, 만약 저장된 값이 없으면 기본값으로 섭씨를 반환시켰다.


메뉴 업데이트와 체크 표시 관리

메뉴의 체크 표시 상태가 즉시 반영될 수 있게 NotificationCenter를 활용해서 온도 단위가 변경될 때 설정 버튼의 메뉴를 다시 생성하고 업데이트를 시켜줬다.

코드 구현

private func setupNotificationObservers() {
    NotificationCenter.default.addObserver(self, selector: #selector(updateMenu), name: .tempUnitChanged, object: nil)
}

@objc private func updateMenu() {
    if let settingsButton = navigationItem.leftBarButtonItem {
        settingsButton.menu = MenuHelper.createSettingsMenu()
    }
}

setupNotificationObservers
tempUnitChanged 알림을 감지해서 메뉴를 업데이트하는 옵저버를 등록했다.

updateMenu
변경된 상태를 반영한 새로운 메뉴를 생성해 설정 버튼에 다시 할당해준다.


현재 구현 상태는

UIMenuUserDefaults를 활욯해서 온도 단위 설정이 앱 전반에서 동기화되고 유지되도록 구현만 했다.

선택한 단위는 앱 종료 후에도 유지가 되고 메뉴의 체크 표시로 현재 상태를 바로바로 직관적으로 확인할 수 있다.

그러니까 지금은 UI와 설정 동기화까지만 구현된 상태인 거고 다음단계에서는 API와 컨트롤러 사이의 바인딩 로직을 추가해서

앱 전체 화면에서 섭씨와 화씨 단위로 온도 데이터를 변환 및 표기하는 기능을 넣어볼 계획이다.


음...

이번 구현을 하면서 UIMenuUserDefaults를 이용한 설정 관리의 기초를 다시 다져보는 시간이었다.

사용자가 선택한 설정을 앱 전반에 적용하고 유지할 수 있는 기능은 사용자 경험을 개선한다고 하니

마지막까지 신경을 잘 써서 추후 API 바인딩까지 연결되면 완성된 기능으로 한번 더 글을 써봐야겠다.

혹시라도 더 나은 방법이나 개선 사항이 있다면 댓글로 공유해주세요 선배님들

profile
iOS 좋아. swift 좋아.

1개의 댓글

comment-user-thumbnail
2025년 1월 11일

오.. 저 메뉴 화면 UIMenu랑 UIAction으로 구현하는 거였군여! 링크 블로그에서 설명한 것처럼 코드가 뭔가 Alert이랑 비슷해 보이네용

답글 달기

관련 채용 정보