인디노트

[강좌] 앱 자체 화면 모드 기능 구현 - macOS 본문

소스 팁/Objective C, Swift, iOS, macOS

[강좌] 앱 자체 화면 모드 기능 구현 - macOS

인디개발자 2022. 11. 4. 17:54

이번 시간에는 macOS 용 앱을 개발하실 때 화면 모드 (예; 라이트, 다크, 자동) 를 앱에서 구현하는 방법에 대하여 알아보도록 할께요.

일반적으로, OS X 의 환경 설정에서 다음과 같이 macOS 자체의 화면모드를 설정하여 사용하게 되는데요. 이 경우에 설치되는 앱에서 별도로 화면 모드를 지정하지 않아도 시스템의 설정값에 따라서 앱 실행 화면 자체의 색상이 자동으로 적용되고 있어요.

본 강좌의 목적으로 여러분께서 직접 개발하시는 앱에 대하여 이렇게 시스템에서 정해지는 화면 모드를 따라가지 않고 앱 스스로 화면 모드를 선택할 수 있는 기능을 구현하는 방법에 대해서 알아 보는 것으로 이해 하시면 되실것 같아요.

macOS Ventura 에서의 화면 모드 선택

 

 

단계 1: 앱 기획

이를 위해서 우리는 다음과 같은 앱을 개발 할 예정이예요.

 

이제 앱 기획은 어느정도(?) 되었으니 앱을 개발해 볼 거예요. 우선 이와 같은 내용의 앱을 개발할 때 우리는 Windows 앱, macOS 앱, iOS 앱, Android 앱 이렇게 주요 타겟을 하는 앱들을 개발 할 수 있는데요. 각 OS 마다 기획을 따로 하지 않고 우리는 위와 같은 단순한 기획만을 가지고 진행할 예정이예요.

우선 이번 강좌에서는 macOS 앱을 타켓으로 할께요. 물론, 다른 타켓의 앱도 강좌를 준비할 예정이기 때문에 구독과 좋아요 및 알림 설정을 꼭 해 두시면 좋으실것 같아요.

이번 강좌는 macOS 앱을 만들기 때문에 Xcode 를 사용할 거예요. 강좌는 기본적으로 처음 개발하시는 분들을 위해서 기초적인것 부터 시작하게 되는데요. Xcode 를 잘 다루실 수 있거나 경험이 많은 분들은 단계 2 는 껌이기 때문에 단계 3으로 점프 부탁 드릴께요.

 

단계 2: 프로젝트 생성

프로젝트 이름은 강좌의 편의상 screenModeControll 이라고 정할께요.

이제, Xcode 에서 File => New => Project... 메뉴를 이용하여 새로운 프로젝트를 만들수 있다는것은 아실 거예요. 다음에 나열된 화면 캡춰들을 참조하여 진행해 주시면 되실 것 같아요.

Xcode 의 File => New => Project... 메뉴를 선택 합니다.

 

상단의 macOS 탭을 선택해 주시고 Applications 섹션에 있는 App 을 선택하신 후 [Next] 버튼을 클릭하여 진행 하실께요.

Product Name 에 screenModeControl 으로 입력하시고 다른 내용은 여러분들의 각자 사용하시는 개발자 계정에 대한 정보를 입력하시면 되실 거예요. 또한, 편의상 SwiftUI 및 Swift 로 진행 할께요. Objective-C 에 대한 강좌는 별도로 준비 할께요. 본 강좌 채널에 대해서 구독과 좋아요 및 알람 설정을 해 두시면 좋을 것 같아요.

이제 프로젝트를 생성할 폴더를 선택하시고 [Create] 버튼을 클릭하시면 우리가 만들 앱의 프로젝트가 생성될 거예요. 강좌의 편의상 저는 learn 폴더에 생성해 볼께요.

 

위의 내용의 절차로 하시면 다음과 같은 Xcode 상의 프로젝트가 될 거예요. 뭔가 된것 같죠? 이미 Xcode 에서 Hello, world! 라는 문구가 표시되는 템플릿을 제공하고 있어요. 그래서, 우리 개발자들이 편리하고 신속하게 앱 개발을 시작할 수 있어요. 저는 참 고마운 일이라고 생각하고 있어요.

우선, 실행을 한번 해 볼께요. Xcode 왼쪽 위에 있는 (삼각형 모양) 실행 버튼을 이용하여 실행해 볼께요. 물론 Xcode 에 대해서 익숙하신 분들은 나름데로의 핫키를 이용하여 신속하게 실행을 하실 거예요. 본 강좌는 기초를 배우러 오신 분도 계시니까 실행 버튼을 이용 할께요.

짠~~~ 이렇게 멋진 앱이 하나 실행 되네요. 당근 Hello, world! 라는 프로그래머 세계에서 기초가 되는 문구도 함께하고 있어요.

macOS 에서 다크 모드로 설정되었을 때의 앱 실행 화면

 

 

단계 3: 앱 코드 구현

본격적으로 앱 코드를 구현해 볼께요. 그에 앞서 우리는 먼저 확인해 볼게 있어요. 현재, 앱이 macOS 시스템의 라이트 모드에서는 어떻게 화면을 표시 하는지 확인 하고 진행 할께요. 위에 실행된 화면은 macOS 시스템에서 "다크 모드" 로 설정된 경우에서 실행된 화면 이예요. 이제 macOS 설정에서 "라이트 모드" 로 설정하고 앱을 실행해 볼께요.

macOS 에서 라이트 모드로 설정되었을 때의 앱 실행 화면

 

이렇게 macOS 의 화면 모드에 따라서 앱 화면 색상들이 바뀌는 것을 알 수 있어요.

본 강좌에 오신 분들은 아마도 자신이 만든 앱이 이렇게 macOS 시스템에서 설정된 화면 모드를 따라가는게 아니고 앱 스스로 화면 모드를 선택하는 기능을 구현하고 싶으신 분들이시잖아요. 그래서 이제부터 원하시는 기능을 구현하는 방법을 코드와 함께 설명해 드릴께요.

개발 경험이 많고 잘 아시는 분들은 본 강좌의 코드 스니펫 혹은 깃허브의 샘플 코드 만을 활용하셔도 충분하실 것 같아요.

 

onAppear

SwiftUI 에는 View 에서 onAppear 이벤트 콜 함수를 사용할 수 있는데요. Appear 은 영어로 직역하면 "나타나다" 정도로 해석할 수 있어요. 이 단어 앞에 on 을 붙히는것은 일반적인 프로그램에서 이벤트 함수를 뜻하고 있어요. 이제 onAppear 이라는 것은 "나타났을때 호출되는 이벤트 함수" 정도로 이해하실 수 있으실 거예요. 이런 규칙을 아는것도 프로그래밍을 배우는데 조금 도움이 되실 수 있을것 같아요.

이 onAppear 을 VStack 이 나타날 때 수행될 수 있도록 코드를 만들거예요. 다음과 같이 코드를 수정해 주세요.

19 라인에 onAppear 을 추가 했어요

 

이렇게 한 후 실행을 해 볼께요. 우리는 수시로 해당 앱을 개발하는 과정에 실행 해 볼 예정이예요. 그것은 앱을 개발하면서 그 과정 과정이 어떻게 동작에 영향을 주는지 확인을 해 보는 과정이라고 생각하시면 되실 거예요.

앱이 실행되면서 Xcode 하단의 출력기에 >>> onAppear 이 출력됨

 

이렇게 앱이 실행되었어요. 여기서 중요한 것은 앱의 실행 화면보다 Xcode 의 하단 출력기에 ">>> onAppear" 이 출력되는 것이예요. 이것은 바로 조금 전 코드를 추가한 onAppear 안에 넣은 print 처리 루틴으로 인해서 출력 되는 것이예요.

결과적으로 여기에서 .onAppear 은 해당 View 가 표시될 때 실행된다는 것을 알 수 있어요. 이제 우리는 화면 모드를 정해주는 코드를 여기에 넣을 거예요. 앱의 화면이 표시될 때 앱의 화면 모드를 정해주는 것이예요.

여기서 잠깐, 현재 우리가 개발하는 macOS 의 화면 모드는 "다크 모드" 예요. 경험상 많은 개발자 분들이 대부분 어두운 배경을 좋아하는 것 같아요. 물론 밝은 계통을 좋아하는 분들도 계실 거예요.

우리는 이러한 "다크 모드" 상태에서 우리가 만드는 앱은 "라이트 모드" 로 동작되도록 해 볼 거예요.

21 라인에 aqua 라는 appearance 로 설정하는 코드 추가

 

21 라인에 NSApp.appearance = NSAppearance(named: .aqua) 라는 코드를 추가 했어요. "Appearance" 를 직역하면 "모습" 이라는 뜻이예요. 즉, 앱의 모습을 아쿠아 (aqua) 로 설정하는 것이 되는 거예요.

다시 한번 우리는 앱을 실행해 볼 거예요. 실행하는 방법은 이제 다들 아시고 계실 거예요.

macOS 의 다크 모드 설정에서도 이렇게 앱의 실행 화면이 라이트 모드로 실행되었어요.  이제, 어두운 모드는 어떻게 되는지 알아 볼께요.

화면 모드를 어둡게 하는 코드

예상 하셨겠지만 앞에 dark 를 붙히는 거예요. aqua 대신 darkAqua 를 사용하면 어두운 모드가 되는 거예요. 이것은 별도로 실행은 안해 볼께요. 여러분은 한번 실행 해 보세요.

이쯤에서 우리의 기술적인 목적을 달성했지만, 우리가 처음에 기획한 최종 목적은 사용자가 다음과 같은 3가지 형태로 앱의 화면 모드를 설정하는 기능이었어요.

  • 라이트 모드
  • 다크 모드
  • 자동 모드 (macOS 설정에 의해서 앱의 화면 모드가 설정됨)

그러기 위해서 우리가 준비해야 할 것은, 바로 다음과 같은 변수를 준비하고 이 변수에 의해서 화면 모드를 설정하도록 하는 것이예요. 바로 다음과 같이 코드를 수정해 주세요.

다음의 코드 스니펫을 사용하시면 편리할 것으로 생각 되는데요. 활용해 보세요.

enum ScreenMode {
	case Light
	case Dark
	case Auto
}

struct ContentView: View {
	@State var screenMode: ScreenMode = ScreenMode.Auto
	
	var body: some View {
		VStack {
			Image(systemName: "globe")
				.imageScale(.large)
				.foregroundColor(.accentColor)
			Text("Hello, world!")
		}
		.padding()
		.onAppear() {
			print(">>> onAppear")
			updateScreenMode()
		}
		.onChange(of: screenMode, perform: {newValue in
			print(">>> onChange to \(newValue)")
			updateScreenMode()
		})
	}
	func updateScreenMode() {
		switch(screenMode) {
			case ScreenMode.Light:
				print(">>> Set to Light")
				NSApp.appearance = NSAppearance(named: .aqua)
				break
			case ScreenMode.Dark:
				print(">>> Set to Dark")
				NSApp.appearance = NSAppearance(named: .darkAqua)
				break
			default:
				print(">>> Set to Auto")
				NSApp.appearance = nil
				break;
		}
	}
}

 

이제 여기서 처음 구현한 코드가 있어요. 기초를 위해서 설명 드릴께요. 물론 아시는 분들은 다음으로 패스 하시면 되고요.

enum

enum ScreenMode {
	case Light
	case Dark
	case Auto
}

이 부분은 enum (enumeration) 이라는 코드인데요. 어떠한 값들을 열거하는 정도로 해석하시면 되는데요.

우리가 프로그램을 개발하다 보면 어떤 값들이 뜻하는 바를 명시적으로 하는게 좋아요. 그것은 코드를 작성하고 유지보수 하는데 매우 중요하기 때문이예요. 예를 들어 특정 값이 1, 2, 3 과 같이 숫자로만 표시한다면 우리는 해당 코드를 이해하기 어려울 수 있어요. 그래서 컴파일러 개발자들이 enum 이라는 훌륭한 생각을 해낸 거예요.

updateScreenMode

func updateScreenMode() {
	switch(screenMode) {
		case ScreenMode.Light:
			print(">>> Set to Light")
			NSApp.appearance = NSAppearance(named: .aqua)
			break
		case ScreenMode.Dark:
			print(">>> Set to Dark")
			NSApp.appearance = NSAppearance(named: .darkAqua)
			break
		default:
			print(">>> Set to Auto")
			NSApp.appearance = nil
			break;
	}
}

이 코드는 우리가 목적하는 기능을 수행하는 함수를 구현한 거예요. updateScreenMode 라는 함수를 호출할 수 있고 이 함수가 호출되면 screenMode 에 설정된 값에 의해서 NSApp.appearance 값을 설정하는 단순한 기능을 수행하고 있어요.

onChange(of, perform)

그리고, 다음과 같이 onChange 라는 이벤트 콜 함수를 추가 했어요. 이 이벤트 콜 함수는 screenMode 의 변수값이 변경되면 호출되는 함수입니다. 즉, screenMode 값이 변경되면 updateScreenMode 함수를 호출하여 실행하게 되는 것이예요.

.onChange(of: screenMode, perform: {newValue in
	print(">>> onChange to \(newValue)")
	updateScreenMode()
})

 

우리는 이렇게 앱의 실행 화면 모드를 특정한 변수의 값으로 설정하는 앱 코드를 작성 했어요. 물론, 코드를 아직 완성한 것은 아니예요. 사용자가 버튼을 눌러서 화면 모드를 정하는 UI 코드를 작성해야만 앱을 기획한대로 코드가 완성되는 것이죠.

이제 앱의 UI (User Interface) 를 구현해 볼께요.

 

단계 4: 앱 UI 구현

SwiftUI 를 이용하면 프로그래머에게 매우 편리성을 줄 수 있어요. 바로 실행되는 코드와 UI 레이아웃을 한 곳에서 할 수 있으며, 이로 인해 매우 빠른 코드 작성을 할 수 있다는 장점이 있어요.

우리는 Hello, world! 라는 텍스트 바로 아래에 버튼을 3개 만들어서 각 버튼에 기능을 부여하여 기획 했던 앱 기능을 수행할 거예요. 지금부터 그것을 설명 해 드릴께요.

다음과 같이 코드상의 Text("Gello, world!") 아래쪽에 HStack 과 3개의 Button 들을 이용하여 버튼이 가로 방향으로 나열되는 UI 코드를 작성했어요. 각 버튼은 "Light", "Dark", "Auto" 라고 표시하며, 각 버튼을 눌렀을 때 실행되는 코드도 각각 screenMode 라는 변수의 값을 변경하도록 작성 되어 있어요.

screenMode 의 값을 변경하는 3개의 버튼을 구현 했어요. Xcode 에서 SwiftUI 를 작성하면 오른쪽에 UI 가 실행된 상태의 화면을 미리 볼 수도 있어요.

 

이제 앱을 실행해 볼께요. 

짠~~~ 다음과 같이 앱이 실행되면 일단은 성공한 것이예요.

이제 버튼을 클릭하여 각 버튼이 동작하는지 확인해 보세요.

[Light] 버튼을 누르면 다음과 같이 "라이트 모드" 의 화면으로 표시되는 것을 확인하시면 앱은 성공적으로 동작하는 것이 되는거죠.

[Dark] 모드와 [Auto] 모드 "다크 모드" 로 동작하게 된답니다. 그 이유에 대해서 설명하면요. 우리가 현재 사용한 macOS 의 화면 모드 설정이 "다크 모드" 이기 때문이예요. 즉, "Auto" 는 macOS 설정에서 설정된 화면 모드를 따라가는 것이예요.

 

다음은 지금까지 작성된 코드의 앱을 실행하여 각 버튼을 클릭하여 화면 모드를 변경해 보는 내용의 캡춰인데요. 여러분들이 작성한 앱을 실행하여 이렇게 동작하는지 확인해 보세요. 만약 이렇게 잘 된다면 macOS 의 환경 설정에서 화면 모드를 "라이트 모드" 로 설정 한 후 [Auto] 버튼의 동작이 "라이트 모드" 와 같은지 확인해 보시면 더욱 좋을 것 같아요.

 

 

단계 5: 앱 상태 값 저장하기 및 읽어오기

이제 우리는 앱 개발의 마지막 단계까지 왔어요. 이렇게 단번에 여기까지 오신것을 보면 여러분들은 정말 훌륭하신 것 같아요. 여러분 스스로도 훌륭한 앱 개발자로써 자부심을 가질 수 있길 바랄께요.

마지막 단계는, 사용자가 설정한 화면 모드를 앱의 샌드박스 저장공간에 저장 한 후 앱이 다시 시작될 때 저장된 값을 읽어와서 사용자가 설정한 화면 모드를 계속 유지시켜 주는 역할을 하는 코드를 작성할 거예요.

우리는, 여기에서 앞에서 작성한 코드 중에서 enum 부분을 수정 할 필요가 있어요. 또한, UserDefaults 라는 클래스를 사용해서 screenMode 를 저장하고 읽어오는 코드를 작성하여 앱을 완성할 단계가 되었답니다.

여러분께서 다음의 코드 스니펫을 참조하여 앞에서 작성했던 ContentView 소스를 완성해 보세요.

그리고, 수정된 enum 부분과 새롭게 작성된 saveScreenMode 및 loadScreenMode 함수를 확인해 보시고, 이 함수들을 어디서 호출하고 있는지 확인 해 보시면 될 것 같아요.

이렇게 완성된 코드로 앱을 실행하여 화면 모드를 선택한 후, 앱을 재실행하여 이전에 선택한 화면 모드가 재실행 된 앱에서 계속 유지되는지 확인해 보세요.

//
//  ContentView.swift
//  screenModeControl
//
//  Created by GYUYOUNG KANG on 2022/11/04.
//

import SwiftUI

enum ScreenMode : Int {
	case Light = 1
	case Dark = 2
	case Auto = 3
}

struct ContentView: View {
	@State var screenMode: Int = ScreenMode.Auto.rawValue
	
	var body: some View {
		VStack {
			Image(systemName: "globe")
				.imageScale(.large)
				.foregroundColor(.accentColor)
			Text("Hello, world!")
			HStack {
				Button("Light") {
					screenMode = ScreenMode.Light.rawValue
				}
				Button("Dark") {
					screenMode = ScreenMode.Dark.rawValue
				}
				Button("Auto") {
					screenMode = ScreenMode.Auto.rawValue
				}
			}
		}
		.padding()
		.onAppear() {
			print(">>> onAppear")
			loadScreenMode()
			updateScreenMode()
		}
		.onChange(of: screenMode, perform: {newValue in
			print(">>> onChange to \(newValue)")
			updateScreenMode()
			saveScreenMode()
		})
	}
	func updateScreenMode() {
		switch(screenMode) {
			case ScreenMode.Light.rawValue:
				print(">>> Set to Light")
				NSApp.appearance = NSAppearance(named: .aqua)
				break
			case ScreenMode.Dark.rawValue:
				print(">>> Set to Dark")
				NSApp.appearance = NSAppearance(named: .darkAqua)
				break
			default:
				print(">>> Set to Auto")
				NSApp.appearance = nil
				break;
		}
	}
	func saveScreenMode() {
		UserDefaults.standard.set(screenMode, forKey: "screenMode")
		UserDefaults.standard.synchronize()
	}
	func loadScreenMode() {
		if (UserDefaults.standard.object(forKey: "screenMode") != nil) {
			screenMode = UserDefaults.standard.integer(forKey: "screenMode")
		}
	}
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

 

 

완성!

정말로 흥미진진하지 않나요? 이렇게 하나하나 해 보시면서 여러분의 앱 개발을 현실화 시켜 보세요.

이번 강좌가 도움이 되셨길 바랄께요. 또한, 다음 강좌를 빠른 방법으로 확인 하실 수 있는 방법은, 본 채널의 구독과 좋아요 그리고 알람 설정을 해 두시면 도움이 되실 거예요.

유튜브 채널은 다음을 이용하시면 되실 거예요.

https://youtube.com/watch?v=ayGbkfJY-FQ&feature=share&si=EMSIkaIECMiOmarE6JChQQ 

 

또한, 본 강좌에서 완성된 소스 프로젝트는 https://github.com/appresorg/learn 에서 찾아보실 수 있으세요.

 

GitHub - appresorg/learn

Contribute to appresorg/learn development by creating an account on GitHub.

github.com

 

수고하셨습니다. 

 

반응형
Comments