swift와는 직접적으로 연관되어 있지는 않지만, 카카오톡 소셜 로그인 기능을 구현하다가 필요해서 글을 남긴다.
CocoaPods는 iOS 및 macOS 개발에서 필수적인 라이브러리 관리 도구이다. 수천 개의 오픈소스 라이브러리를 손쉽게 프로젝트에 통합하고 관리할 수 있게 한다. (python에서의 pip라고 생각하면 좋을 지도?) 이번 포스팅에서는 Cocoapods의 개념, 설치 방법, 그리고 실제로 프로젝트에서 사용하는 방법을 자세히 설명해 보겠다.
1. CocoaPods란?
CocoaPods는 Xcode 프로젝트에 외부 라이브러리를 쉽게 통합하고 관리할 수 있게 해주는의존성 관리 도구이다. 오픈 소스 라이브러리뿐만 아니라 사내에서 개발한 라이브러리도 쉽게 추가, 업데이트, 삭제할 수 있으며, 라이브러리 간의 의존성을 자동으로 해결해준다.
2. CocoaPods 설치
CocaPods를 사용하기 위해서는 먼저 설치가 필요하다. 설치는 RubyGems를 통해 간단하게 할 수 있다. 먼저 터미널을 열고, 다음 명령어로 CocoaPods를 설치한다. 만약 RubyGems 버전이 낮다면 버전을 올린 뒤 설치한다.
sudo gem install drb -v 버전
sudo gem install cocoapods
설치가 완료되면 CocoaPods 버전을 확인하여 제대로 설치되었는지 확인할 수 있다.
pod --version
3. CocoaPods 설정
CocoaPods를 설치하고 나서 Xcode 프로젝트에 설정하는 방법을 알아보자. 먼저 Xcode 프로젝트가 있는 디렉토리로 이동한다. 그리고 Podfile을 생성해준다. Podfile은 프로젝트에서 사용할 라이브러리 목록을 정의하는 파일로, bash에서
pod init
으로 생성할 수 있다. 꼭 /project 에서 생성해주자. 그리고 뭐든 편집기를 이용하여 Podfile을 수정해주자. Podfile을 열어 사용할 라이브러리를 추가한다. 나는 kakao developers에서 안내하는 대로 작성했다.
카카오 로그인을 위한 Podfile 설정
안내에는 이것저것 많이 알려줬지만, 사실 필요한건 KakaoSDKCommon과 KakaoSDKAuth, KakaoSDKUser 인듯하다. Podfile을 수정했다면, 다음 명령어를 실행하여 라이브러리를 설치하자.
pod install
이 명령어는 'Podfile'에 명시된 모든 라이브러리를 다운로드하고, Xcode 프로젝트를 위한 새로운 '.xcworkspace'파일을 생성한다. 앞으로는 '.xcodeproj'파일이 아니라, '.xcworkspace'파일을 사용해야 한다. 이 파일을 사용하여 Xcode에서 프로젝트를 열고 작업을 시작한다.
4. CocoaPods 사용 시 유의사항
Pod 업데이트: 라이브러리를 업데이트하려면 'Podfile'을 수정한 후, 'pod update'라는 명령어를 실행하여 업데이트 할 수 있다. 특정 라이브러리만 업데이트하려면 'pod update 라이브러리'와 같이 라이브러리 이름을 명시할 수 있다.
Pod 삭제: 사용하지 않는 라이브러리를 삭제하려면 'Podfile'에서 해당 라이브러리 항목을 제거한 후, 'pod install' 명령어를 실행한다.
Pod 버전 고정: 특정 버전의 라이브러리를 유지하려면 'Podfile'에서 버전을 명시해두는 것이 좋다. 그러면 업데이트 시에도 이 버전이 유지된다.
UITextField를 사용해서 유저로부터 텍스트 입력을 받는 기능을 만들게 되었다. 아마 거의 모든 앱에서 사용될 기능이 아닐까 싶은데, 생각보다 간단하면서도 그와중에 고군분투의 흔적이 있어 기록으로 남겨본다.
1. storyboard에서 UITextField 생성하고 배치하기
Text Field를 storyboard에 추가
필요한 UI 요소를 드래그앤드롭으로 storyboard에 추가하고 constraints를 설정하여 원하는 위치에 예쁘게 배치해준다. 이제는 기계적으로 하는 작업이 되어버렸다. 그리고 storyboard 옆에다 ViewController를 열어 control+드래그로 IBOutlet을 연결한다. IBOutlet의 사용방법을 모른다면 이전 글을 참고하자. commentText라는 변수명으로 ViewController 파일에서 댓글 텍스트 영역을 동적으로 접근하고자 한다.
@IBOutlet weak var commentText: UITextField! // 댓글 텍스트 영역
2. 각종 꾸미기 옵션들(borderWidth, borderColor, placeholder)
이제 commentText를 예쁘게 꾸며주는 몇 가지 작업들을 거쳐본다. viewDidLoad() 메서드 내부에 borderwidth, borderColor를 설정하는데, borderColor라는 변수는 휴대폰 기기의 설정이 다크 모드이면 흰색, 라이트 모드이면 검은색으로 주었다. 그리고 placeholder 역시 UITextField가 비활성화 된 상태와 활성화 된 상태를 다르게 설정하였다. 매번 isEnabled를 바꿔줄때마다 updateCommentPlaceholder() 메서드를 수동으로 호출해야 하는지는 의문이지만, 추후에 공부하기로 하고 일단은 번거로워도 그냥 호출한다.
@IBOutlet weak var commentDiv: UIView! // 댓글 영역
@IBOutlet weak var commentImg: UIImageView! // 댓글 흑백 아이콘
@IBOutlet weak var commentText: UITextField! // 댓글 텍스트 영역
override func viewDidLoad() {
// 생략
let borderColor = UIColor { traitCollection in
return traitCollection.userInterfaceStyle == .dark ? UIColor.white : UIColor.black
}
// 댓글 텍스트 영역의 border 옵션 설정
commentText.layer.borderWidth = 1.0
commentText.layer.borderColor = borderColor.cgColor
commentText.isEnabled = false // 최초에는 비활성화 된 상태로 시작
self.updateCommentPlaceholder()
}
func updateCommentPlaceholder() {
if commentText.isEnabled {
commentText.placeholder = "00님의 생각을 공유해주세요."
} else {
commentText.placeholder = "선택지를 고른 후 댓글을 남길 수 있어요."
}
}
이제 여기까지 왔으면 꽤 멀쩡해보이는 화면이 만들어 진 듯하다.
3. 가상 키보드 높이를 감지하여 위치 이동하기
UITextField에 별도의 IBAction을 지정하지 않아도 기본적으로 터치 시, 아이폰의 가상 키보드를 열어주는 역할을 하나보다. 그런데 문제는, 화면의 최하단(정확히 말하면 safearea의 최하단)에 고정되어 있는 UITextField가 가상 키보드가 나타나면서 같이 따라 올라오지 않아 눈에 보이지 않는다는 것이다. 이를 NotificationCenter를 이용해서 해결한 방법을 공유하고자 한다.
override func viewDidLoad() {
super.viewDidLoad()
// 생략
// 댓글 영역 배경색 설정
commentDiv.backgroundColor = UIColor { traitCollection in
return traitCollection.userInterfaceStyle == .dark ? UIColor.black : UIColor.white
}
// 댓글 영역 그림자 설정
self.addShadowToCommentDiv(view: commentDiv)
// 댓글 텍스트 영역의 border 옵션 설정
commentText.layer.borderWidth = 1.0
commentText.layer.borderColor = borderColor.cgColor
// 키보드 프레임 변경 노티피케이션 등록
NotificationCenter.default.addObserver(self, selector: #selector(updateKeyboardFrame(_:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
commentText.delegate = self // 텍스트 필드 델리게이트 설정
}
@objc func updateKeyboardFrame(_ notification: NSNotification) {
if let userInfo = notification.userInfo,
let keyboardFrameEnd = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue,
let animationDuration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double,
let animationCurveRawNSN = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber {
// Safe Area Insets 가져오기
let bottomSafeAreaInset = view.safeAreaInsets.bottom
// 키보드 높이에서 Safe Area를 고려한 높이 계산
let adjustedKeyboardHeight = keyboardFrameEnd.height - bottomSafeAreaInset
// 키보드 애니메이션의 곡선 및 지속 시간 가져오기
let animationCurveRaw = animationCurveRawNSN.uintValue
let animationOptions = UIView.AnimationOptions(rawValue: animationCurveRaw << 16)
// commentDiv의 위치를 키보드 높이에 맞춰 애니메이션으로 변경
UIView.animate(withDuration: animationDuration,
delay: 0,
options: animationOptions,
animations: {
if keyboardFrameEnd.origin.y >= UIScreen.main.bounds.height {
// 키보드가 내려간 상태면 원래 위치로 복원
self.commentDiv.transform = .identity
} else {
// 키보드가 나타난 상태면 Safe Area를 고려하여 올리기
self.commentDiv.transform = CGAffineTransform(translationX: 0, y: -adjustedKeyboardHeight)
}
}, completion: nil)
}
}
먼저 Notification 등록은, 키보드 프레임이 변결될 때 발생하는 노티피케이션으로, 키보드의 크기나 위치가 변경될 때(ex: 키보드가 올라오거나 내려갈 때, 혹은 회전할 때 등)에 해당 이벤트를 감지할 수 있다. UIResponder.keyboardWillChangeFrameNotification 외에도 UIResponder.keyboardWillShowNotification과 UIResponder.keyboardWillHideNotification을 사용하는 방법도 있지만, 이렇게 해보니 가상키보드의 영역이 입력 도중 변화할 때(ex. 한국어 -> 일본어로 언어 전환을 하면 일본어 키보드가 한자변환 추천 영역 때문에 한국어나 영어보다 사이즈가 크다. 또한 이모지 입력시에도 키보드 크기가 변화한다)를 감지하지 못하는 문제가 있었다.
가상키보드 영역의 높이를 감지해서 IBOutlet으로 연결한 UIView(또는 UITextField)의 위치를 updateKeyboardFrame 이라는 메서드에서 변경해준다. 이때, 단순히 키보드 높이만큼 translate하는 것이 아니라, SafeAreaInset을 고려해서 높이를 조정한다. 그냥 위로 올려버리면 텍스트 입력 필드 아래에 빈 공간이 생기게 된다. safearea에 대해 조금 더 공부하고 별도의 포스팅으로 올려야겠다.
4. 입력 완료 이후 가상 키보드 내리기
이제 유저가 텍스트 입력을 완료하면, 가상 키보드를 적절한 방식으로 없애 주어야 하는데, 이 부분이 잘 처리되지 못한 앱을 종종 본 적이 있다. 나는 우선 유저가 UITextField 외의 다른 영역을 터치하면 키보드가 사라지는 방식을 사용했다.
// 화면의 빈 공간을 터치하면 호출되는 메서드
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
// 현재 화면에서 키보드를 숨김
self.commentText.endEditing(true)
}
이 외에도 아래로 스와이프를 통해 키보드를 내리는 카카오톡, 리턴(엔터)키 입력으로 텍스트를 전송하며 키보드를 사라지게 하는 방식 등 다양한 방식들이 사용되고 있다. 스와이프로 키보드를 내리는 방식은 구현에 도전해봐야겠다.
'IBOutlet'은 변수 앞에 '@' 기호를 붙여서 선언한다. 보통 'weak' 참조로 선언되는데, 이는 메모리 관리 측면에서 순환 참조(circular reference)를 방지하기 위함이다. UI 요소는 스토리보드에서 설정되기 때문에, 'nil' 상태일 수 있으며, 따라서 옵셔널로 선언하는 경우가 많다.
'IBAction'이란?
IBAction(Interface Builder Acton)은 사용자 인터페이스에서 발생하는 이벤트(ex: 버튼 클릭, 슬라이더 값 변경 등)에 대한 액션을 처리하기 위해 사용되는 메서드를 나타낸다. 즉, 사용자의 인터랙션에 반응하는 함수와 인터페이스 빌더의 요소를 연결하는 방법이다.
스토리보드에서 버튼 등 이벤트를 발생시키는 UI 요소를 선택한 후, 이를 Swift 코드로 드래그하면 자동으로 '@IBAction'이 붙은 메서드로 연결된다. 이 메서드는 해당 이벤트가 발생할 때 호출된다. 아래는 스토리보드에서 버튼을 추가하고, 이 버튼이 클릭될 때 실행될 액션을 정의하는 예시이다. 버튼을 클릭했을 때, 레이블의 텍스트를 변경하는 동작을 수행한다.
swift를 이용한 iOS 앱 개발 입문 2: 앱(App), 씬(Scene), 생명주기(Lifecycle)
늘상 웹개발만 하다가, 앱개발의 세계에 처음 발을 담궈보니, 앱의 생명주기라는 것에 대해 알 필요가 있어서 정리해본다. 이전 글에서 앱의 생명주기를 'AppDelegate.swift'라는 파일에서 관리한다고 적었는데, 조금 더 깊이 들어가보자.
앱의 생명주기(App Lifecycle)은 iOS앱이 실행되는 동안 다양한 상태 전환을 설명하는 중요한 개념이다. 앱의 생명주기를 이해하면 앱의 시작, 중지, 다시 시작, 종료 시 적절한 작업을 수행할 수 있다. 다음은 iOS앱의 생명주기와 각 상태에 대한 자세한 설명이다.
1. 앱의 상태
앱은 다음과 같은 상태를 가질 수 있다:
Not Running: 앱이 실행되지 않음
Inactive: 앱이 포어그라운드에 있지만 이벤트를 처리하지 않음
Active: 앱이 포어그라운드에 있으며 이벤트를 처리하고 있음
Background: 앱이 백그라운드에 있지만 여전히 코드 실행이 가능
Suspended: 앱이 백그라운드에 있으며 코드 실행이 중단됨. 메모리에 유지됨
2. 씬(Scene)은 뭔데?
유사하게 'SceneDelegate.swift'라는 파일에는 씬의 생명주기를 관리하는데, 그럼 씬은 도대체 뭘까 하고 찾아보니... 씬은 iOS 13 이후 도입된 개념으로, 하나의 앱 내에서 여러 개의 윈도우를 관리할 수 있게 한다. 씬은 사용자가 동시에 여러 개의 UI환경을 다룰 수 있도록 지원한다. 씬의 주요 특징으로는,
멀티태스킹 지원: 씬을 사용하면 사용자가 하나의 앱에서 여러 개의 독립적인 UI 환경을 동시에 사용할 수 있다. 예를 들어, iPad에서 멀티윈도우 모드를 사용할 수 있다.
독립적인 생명 주기: 각 씬은 독립적인 생명 주기를 가지고 있으며, 'SceneDelegate'를 통해 관리된다. 각 씬의 생성, 활성화, 비활성화, 종료 등의 이벤트를 개별적으로 처리한다.
윈도우 관리: 씬은 하나 이상의 윈도우를 포함할 수 있으며, 각 윈도우는 자체적인 뷰 컨트롤러 계층을 갖고 있다.
3. 생명주기 이벤트를 처리하는 메서드
위에서 알아본 생명주기의 변화 이벤트를 받아주는 메서드가 존재하여, 'AppDelegate.swift'파일과 'SceneDelegate.swift'파일에서 이러한 이벤트를 핸들링 해 주어야 할 것이다. 아래는 앱과 씬의 생명주기에 관련된 메서드들을 소개한다.
AppDelegate 메서드
application(_:didFinishLaunchingWithOptions:) - 앱이 시작될 때 호출. 앱의 초기 설정을 여기서 진행
applicationDidBecomeActive(_:) - 앱이 활성 상태로 전환될 때 호출. 일반적으로 앱이 포어그라운드로 들어올 때나 일시 정지 상태에서 돌아올 때 호출
applicationWillResignActive(_:) - 앱이 비활성 상태로 전환되기 전에 호출. 전화나 메시지 등으로 인해 일시 중단될 때 호출
applicationDidEnterBackground(_:) - 앱이 백그라운드로 전환될 때 호출. 상태를 저장하거나 리소스를 해제하는 등의 작업을 여기서 수행
applicationWillEnterForeground(_:) - 앱이 백그라운드에서 포어그라운드로 전환될 때 호출
applicationWillTerminate(_:) - 앱이 종료될 때 호출. 상태를 저장하고 정리 작업을 수행
SceneDelegate 메서드
scene(_:willConnectTo:options:) - 새로운 씬이 생성될 때 호출. 씬의 초기 설정을 여기서 진행
sceneDidDisconnect(_:) - 씬이 연결 해제될 때 호출. 씬이 더 이상 사용되지 않음을 의미
sceneDidBecomeActive(_:) - 씬이 활성 상태로 전환될 때 호출
sceneWillResignActive(_:) - 씬이 비활성 상태로 전환되기 전에 호출
sceneWillEnterForeground(_:) - 씬이 백그라운드에서 포어그라운드로 전환될 때 호출
sceneDidEnterBackground(_:) - 씬이 백그라운드로 전환될 때 호출
이렇게 보니까 앱과 씬이 개념적으로는 좀 구분이 되는 듯 하기도 하고... 역시 가장 중요한 것은 직접 부딪히며 코드를 짜보는게 이해하기에 가장 쉽고 빠른 방법인 듯 하다. 사이드 프로젝트 앱의 스플래시 화면을 만들어가며 공부해보자.