-Today's Learning Content-

  • UIButton/Configuration
  • Error

1. UIButton.configuration

개념 정리

UIButton에서 Configuration은 iOS 15 이후부터 사용할 수 있게 된 버튼을 정의할 수 있는 방법이다.

1) 기본적인 버튼 정의

UIKit에서 버튼을 구현하기 위해서는 우선 UIButton의 인스턴스를 생성하고, 버튼의 스타일을 지정하여 생성해야 한다.

// 버튼을 구현하는 방법
let button: UIButton = {
	let button = UIButton()
	button.setTitle("Push", for: .normal)
	button.setTitleColor(UIColor.blue, for: .normal)
    button.titleLabel?.font = .systemFont(ofSize: 30, weight: .black)
	button.translatesAutoresizingMaskIntoConstraints = false
        
	return button
}()

이렇게 버튼을 구현하는 방법은 어렵지는 않지만 커스텀 요소가 부족하다는 생각이 든다. 이 때, Configuration을 사용하면 조금 더 버튼을 자유롭게 꾸밀 수 있다.

2) Configuration으로 버튼 구현하기

Configuration으로 버튼을 구현하는 방법은 일반적인 버튼을 구현하는 방법과 크게 다르지 않다.
우선 버튼과 configuration을 각각 정의해준다.

private let button = UIButton()
private var config = UIButton.Configuration.tinted()

여기서 tinted()는 버튼의 형식을 말하며, 크게 4가지로 분류된다.

TintedFilledGrayPlain

스토리보드에도 비슷한 설정이 있으니 아마 이해하기 쉬울 것이라고 생각한다.

이제 config를 사용하여 버튼의 속성을 변경할 수 있다.

a. 색상 변경하기

  • baseBakGroundColor를 이용하여 백그라운드 색상 변경
config.baseBackgroundColor = .systemBlue
  • baseForegroundColor를 이용하여 컨텐츠 색상 변경
config.baseForegroundColor = .blue

이 때, background.backgroundColor를 이용해서도 백그라운드 색상을 변경할 수 있다. 이렇게 하면 baseBakGroundColor와 달리 버튼 스타일에 영향을 받지 않으면서 색을 지정해줄 수 있다. 또, baseBakGroundColor로 지정한 색보다 우선순위가 높게 적용된다.

config.background.backgroundColor = .red

// backgroundColor보다 우선순위가 낮기 때문에 적용되지 않음
config.baseForegroundColor = .blue

b. 텍스트 설정

텍스트는 TitleSubTitle로 구성되어 있다. 이 데이터를 변경하는 방법은 각각 2개씩 있다.

  • title, subtitle 선언하기
config.title = "Title"
config.subtitle = "subtitle"
  • AttributedString 선언하기
var attr = AttributedString.init("Title")
attr.font = .systemFont(ofSize: 25, weight: .bold)
config.attributedTitle = attr

var subAttr = AttributedString.init("subtitle")
attr.font = .systemFont(ofSize: 15, weight: .light)
config.attributedSubtitle = attr

AttributedString로 텍스트를 설정하면 폰트나 사이즈 등을 자유롭게 설정할 수 있다.

텍스트의 경우 titleAlignment를 사용해서 정렬을 지정할 수도 있지만, 타이틀에 적용하면 서브타이틀도 함께 적용되고, 별도로 정렬을 지정할 수는 없다.
또, titlePadding를 사용하여 타이틀과 서브 타이틀간의 패딩값을 설정해줄 수 있다.

config.titleAlignment = .leading // 왼쪽 정렬
config.titlePadding = 10

c. 이미지 설정

버튼의 콘텐츠로 이미지를 삽입할 수 있다.

config.image = UIImage(systemName: "pencil")

이미지는 위치와 패딩값을 지정해줄 수 있다.

config.imagePadding = 10
config.imagePlacement = .bottom // 위치를 하단으로 설정

d. 버튼 레이아웃 설정

buttonSize, cornerStyle, contentInsets 를 사용하여 버튼의 레이아웃 속성을 설정해줄 수 있다.

buttonSize는 버튼의 사이즈를 설정값에 따라 바꾸지만, 내부 컨텐츠의 크기는 바꿔주지 않는다. 즉, 버튼을 아무리 작게 지정하더라도 내부 컨텐츠 크기보다 작아지지는 못한다.

contentInsets 은 버튼 내부의 컨텐츠와 버튼에 대한 거리 값을 설정해줄 수 있다.

config.buttonSize = .large
config.cornerStyle = .large
config.contentInsets = NSDirectionalEdgeInsets(top: 20,
												leading: 20,
                                                bottom: 20,
                                                trailing: 20)

e. 버튼에 적용하기

이제 만들어진 Configuration을 버튼에 적용시키면 되는데, 방법은 무척 간단하다.

button.configuration = config

버튼의 Configuration을 위에서 만든 config로 설정하면 된다.
이렇게 하지 않고 버튼을 생성할 때 Configuration을 설정해줄 수도 있다.

let button = UIButton(configuration: config)

f. 버튼 이벤트

ConfigurationConfigurationUpdateHandler라는 클로저를 제공하며, 이 클로저를 통해 버튼의 상태에 따른 값을 지정해줄 수 있다.

button.configurationUpdateHandler = {
	switch $0.state {
	case .normal:
		$0.configuration?.image = UIImage(systemName: "wifi.slash")
		$0.configuration?.baseBackgroundColor = .magenta
		$0.configuration?.baseForegroundColor = .systemPink

	case .highlighted:
		$0.configuration?.baseBackgroundColor = .systemPink
		$0.configuration?.baseForegroundColor = .systemPink
		$0.configuration?.image = UIImage(systemName: "progress.indicator")
                
	case .disabled:
		$0.configuration?.baseBackgroundColor = .gray
		$0.configuration?.baseForegroundColor = .lightGray
                
	case .selected:
		$0.configuration?.baseBackgroundColor = .cyan
		$0.configuration?.image = UIImage(systemName: "wifi")
		$0.configuration?.baseForegroundColor = .blue
                
	default:
		break
	}
}

g. 구현 결과물


2. Error

개념 정리

Error란 말 그대로 오류를 나타내는 것으로, 의도하지 않은 형태로 프로그램이 실행되거나 알 수 없는 오류가 발생하는 현상을 뜻한다.

1) do-catch

우리는 일반적으로 에러를 처리할 때 발생 가능한 에러를 정의하는 열거형을 만들고, 에러가 발생할 가능성이 있는 메소드를 만들어 thorows로 에러를 던지도록 만든다.

// 예시
do {
    let x = try testMethod()
    print(x)
} catch pattern1 {
    print("Error: Pattern 1")
} catch pattern2 {
    print("Error: Pattern 2")
} catch {
    print("알 수 없는 에러 발생")
}

pattern1과 pattern2는 개발자가 직접 발생할 수 있는 에러를 열거형으로 선언하고 지정하는 에러이다.
그렇다면 마지막 catch는 무엇인가? 아마도 개발자가 예상하지 못한 에러일 것이다. 때문에 마지막 catch는 보통 발생하지 않을 에러, 혹은 발생 가능성이 극도로 낮은 에러라고 생각했을 것이다.
그렇기 때문에 단순히 어떤 에러인지 출력하게 하거나 return 하는 경우가 많을 것이다.

하지만 만약 마지막 catch문에 잡힌 에러가 치명적인 에러였다면 어떻게 될까?

이럴 때는 앱을 강제로 종료시키도록 프로세스를 강제로 종료시키는 fatalError를 사용할 수도 있다. 이렇게 하면 치명적 오류가 발생하더라도 앱을 강제종료하여 더 위험한 상황을 막을 수 있을 것이다.

그러나

비록 발생 가능성이 매우 낮더라도, 예상치 못한 에러로 앱이 강제종료 되는 경우가 있다면 App Store 심사 지침에 따라 앱스토어 등록이 거부될 수도 있다고 한다.

그렇기 때문에 fatalError를 사용하는 방법은 되도록 지양하는 것이 좋다. 만약 그럼에도 fatalError를 써야하는 상황이 있다면 어떻게 하는 것이 좋을까?

2) graceful termination

애플에서는 오래전 Graceful Termination이라는 우아한 종료에 대해 언급한 적이 있다.

iOS에는 우아한 종료를 위한 기능이 없고, 앱을 종료하기 위해서는 홈버튼이나 제스처를 통한 종료를 해야한다고 한다. 그러나 만약 앱이 치명적인 오류로 종료되어야 한다면 다음의 방법으로 앱을 종료할 것을 권하고 있다.

1. 유저에게 어떤 문제가 발생했는지와 선택 가능한 액션을 Alert 로 보여준다.

2. 갑자기 앱이 꺼지는 것처럼 느껴지지 않게, 홈 스크린으로 돌아가는 애니메이션과 함께 우아하게 앱을 종료시킨다.

그래서 한번 만들어보았다.

특정 뷰에 진입하면 alert가 작동되고, alert에 정해둔 액션으로 앱을 종료하는 메소드가 실행되게 된다.

앱을 종료하는 메소드는 아래의 코드를 사용했다.

// 앱을 강제로 종료시키는 메소드
// second 초 이후 종료된다.
func terminateAppGracefullyAfter(second: Double) {
	// 지정한 시간(second) 이후에 메인 스레드에서 비동기 작업을 실행
	DispatchQueue.main.asyncAfter(deadline: .now() + second) {

	// 앱을 백그라운드로 이동시키는 `suspend` 명령을 호출
	UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
		
        // 백그라운드로 이동한 0.5초 후에 앱을 강제로 종료
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
			exit(1) // Exit Failure
		}
	}
}

-Today's Lesson Review-

오늘은 UIButtonConfiguration으로 정의하는 방법에 대해 학습하고,
Error와 fatalError, 그리고 우아한 종료에 대해 공부했다.
지금까지 앱의 종료를 고려하며 코드를 작성해본 적이 없었는데,
오늘 에러에 대해 공부하며 앞으로는 에러에 대해 어떻게 처리하면 좋을지 생각하게 되는 시간이 되었다.
profile
이유있는 코드를 쓰자!!

2개의 댓글

comment-user-thumbnail
2024년 11월 14일

덕분에 심사지침에 있는 우아한 종료 라는 것도 알게되고 진짜 좋아요.. 근데 과제도 하고 예시도 만들면서 공부하시며 블로그도 작성하시고 같은 시간인데 여러개를 해버리시는데 혹시 헤르미온느이신가여

1개의 답글