✔︎ 오늘의 정리
- setTitle과 Configuration
- stackView로 메세지 화면 만들기
- Add
- alignment
- location
- 화면 전환 코드 리팩토링하기(generic)
SetTitle과 Configuration.attributedText
결론부터 말하자면, Configuration과 SetTitle이 동시에 적용이 안 된다.
둘 다 적용했을 때 setTitle이 우선 적용이 된다. 그러니까...


왼쪽은 버튼에 setTitle을 적용한 경우이고, 후자는 caption을 적용한 것이다.
코드로 확인해 보면 다음과 같다.
처음 코드는 아래와 같다.
static func makeButton(image: UIImage?, title: String) -> UIButton {
let button = UIButton()
button.setTitle(title, for: .normal)
button.setImage(image, for: .normal)
if title.isEmpty {
button.layer.cornerRadius = 15
button.layer.borderColor = UIColor.white.cgColor
button.layer.borderWidth = 1
} else {
var configuration = UIButton.Configuration.plain()
configuration.imagePlacement = .top
button.backgroundColor = .clear
configuration.imagePadding = 10
var attributedTitle = AttributedString(title)
attributedTitle.font = .preferredFont(forTextStyle: .caption2)
configuration.attributedTitle = attributedTitle
button.configuration = configuration
}
button.tintColor = .white
return button
}
보다시피, setTitle이 우선적으로 적용되어 아래 AttributedTitle이 적용되지 않는 걸 알 수 있다.
두 번째 코드
static func makeButton(image: UIImage?, title: String) -> UIButton {
let button = UIButton()
button.setImage(image, for: .normal)
if title.isEmpty {
button.layer.cornerRadius = 15
button.layer.borderColor = UIColor.white.cgColor
button.layer.borderWidth = 1
} else {
var configuration = UIButton.Configuration.plain()
configuration.imagePlacement = .top
button.backgroundColor = .clear
configuration.imagePadding = 10
var attributedTitle = AttributedString(title)
attributedTitle.font = .preferredFont(forTextStyle: .caption2)
configuration.attributedTitle = attributedTitle
button.configuration = configuration
}
button.tintColor = .white
return button
}
setTitle을 빼면 내가 의도했던대로 설정된다!
StackView로 메세지 화면 만들기
stackView로 화면을 만드는데, 원래 잘 떠야 할 화면이 안 떴다.
알고 보니 기존 View에 addSubview Method를 사용해서 넣어줘야 했던 것과 다르게, stackView는 addArrangedSubview로 넣어줘야 하더라!!
근데 일단 하긴 하면서도 맨 첨 든 생각은 왜?!?! 였다
사실 그럴만함...
스택뷰를 두 개 썼는데 이 문제로 왜 안 뜨지?!?!?! 하면서 한참 헤맸기 때문이다.
나는 카톡 대화창처럼 label 크기만큼 나오길 원하는데, 스택뷰는 전체로 보여주었다.


문제가 어디 있을까 하구 많이 헤맸는데, 생각보다 간단했다... ^_^
stackView의 alignment를 leading으로 설정해주면 됐던 것이다!!!
extension UIStackView {
static func stackViewBuilder(space: CGFloat, axis: NSLayoutConstraint.Axis) -> UIStackView {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.alignment = .leading
stackView.axis = axis
stackView.distribution = .equalSpacing
stackView.spacing = space
return stackView
}
}
요런 식으루 말이다.
이전에는 stackView의 alignment를 설정해 주지 않았다.
space를 사이에 끼고 있는 뷰들의 집합을 만들려고 하니 스택뷰를 떠올렸는데, 이전까지는 정말 가득찬 스택뷰만 사용했고 이런 식으로 사용하는 건 처음인 것 같다.
alignment가 생각보다 중요하군아.
화면 전환 코드를 generic 문법을 이용해 리팩토링하기
제네릭 문법을 배웠는데, 막상 내용은 알고 있지만 잘 활용하지는 못하고 있는 것 같아 써 보려고 반복되는 화면 전환 코드를 찾아서 리팩토링해 보았다.
반복됐던 코드는 다음과 같다.
func changeName() {
let sb = UIStoryboard(name: "Main", bundle: nil)
guard let vc = sb.instantiateViewController(withIdentifier: NameSettingViewController.identifier) as? NameSettingViewController else { return }
navigationController?.pushViewController(vc, animated: true)
}
위 코드를 다음과 같이 리팩토링했으며, 축약된 함수의 쓰임은 아래와 같다.
func changeView<T: UIViewController>(VC: T.Type) {
let sb = UIStoryboard(name: "Main", bundle: nil)
guard let vc = sb.instantiateViewController(withIdentifier: T.identifier) as? T else { return }
navigationController?.pushViewController(vc, animated: true)
}
func changeName() {
changeView(VC: NameSettingViewController.self)
}
뚝딱 쓴 거 같은데 막상 쓸 때는 많이 헷갈렸다. 뭐가 헷갈렸냐고 묻는다면 세 개 정도를 꼽을 수 있을 것 같다.
- 임시로 만든 타입인 T가 무엇을 상속받아야 할지 몰랐다.
- 당연히 뷰컨트롤러 아닌가? 싶으면서도 워낙 그냥 뷰를 상속받는 애들도 있기도 하고 뷰를 써야 하나? 싶었는데 그러면 아래 뷰컨트롤러를 띄우는 명령어가 실행이 안 되더라. 생각해 보면 당연한 건데 하기 전에는 왜케 헷갈리게 느껴지는지 모르겠다.
- parameter에서 Type 부분에 그냥 T가 아닌 T.Type을 적어 주어야 한다.
- 이것도... 당연할 수도 있다!!!
- 하지만 T.Type이 바로 생각나지 않았다. 나는 일단 T와 T.self가 먼저 생각났다. T는 일반적인 타입을 말하는 거고, T.self는 타입의 인스턴스를 말하는 것이다. T.Type은 타입 자체를 말하는 거고!!!
- 그냥 말로만 들었을 때는 넘 헷갈린다. 예를 들어 보자.
- 다른 뷰 화면에서 CustomViewController를 정의했다고 하자. 그럼, 이 CustomViewController는 일종의 타입일 것이다. 만약에 커스텀 뷰 컨트롤러 객체를 선언하게 된다면? 타입의 인스턴스를 불러오는 것이다.
- ... ... 별루 좋은 설명은 아니었던 것 같다.
- 음, 다시 생각해 보자. 인과관계를 따지면 조금 더 이해하기 편하다!
- 차라리 아래 예시를 먼저 보자.

선언한 제네릭 타입 T의 경우에는 T라고 사용할 경우 그냥 타입이고, 이 타입을 선언하게 된다면 그 변수는? 스스로를 가리킬 때 VC.self라고 쓸 것이다. (T.self와 맥락상 같음 인스턴스 하나를 가리킨다는 점에서~~!!)
근데 만약 우리가 VC의 파라미터의 타입값에 그냥 T나 T.self를 넣어줄 경우에는 큰 문제가 생긴다.
사실 당연하다고도 볼 수 있다. 우리는 파라미터값을 넣어줄 때 해당 타입 그 자체를 넣어줬지 Int(), Double() 이런 식으로 인스턴스를 생성해서 넣어주지 않았다. 무엇보다 우리가 만든 타입을 파라미터에 쓸 때도 someClass < 이런 식으로 작성했지 거기에 someClass() 이런 식으로 작성하지는 않는다. ㅇ.ㅇ
그렇기에 T.Type (T의 타입 그 자체를 가리키는 것)을 적어주어 타입이라고 명시적으로 선언해 준다고 보면 댄다.
Fatal error: Unexpectedly found nil while implictly unwrapping an Optional Value

TMDB 프로젝트에서 Onboarding 화면을 맹글어 줄 때 갑자기 나와서 엥??? 하면서 좀 헤맸다...
얘는 코드베이스가 아니라 storyboard로 구현되어 있는데, 스토리보드에 있는 친구랑 연결을 안 해줘서 엥? 위치 못 찾는데? 하구 그냥 nil을 뱉더라
수정해 줬다...
근데 아직 문제가 많은 것 같다 ㅠ_ㅠ
일단 프로젝트 자체를 네비게이션바와 탭바를 동시에 사용해서 썻는데
네비게이션바를 넣을 수는 잇다...
근데 탭바를 어떻게 연결해야 할지가?! 감이 안 잡힘
다른 블로그들을 확인해 보면 탭바 컨트롤러를 따로 스토리보드를 분리시킨 후에 storyboard Reference를 이용해서 넣는 것처럼 보이는데...
나는 코드로 화면 전환을 하고 싶은 거라?? 이게 맞는지 모르겠다
g헐햇다
guard let scene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: scene)
if UserDefaultsHelper.shared.haveBeenBefore {
// 처음이 아닐 때
let sb = UIStoryboard(name: "Main", bundle: nil)
guard let tabBarController = sb.instantiateViewController(withIdentifier: "mainTabBarController") as? UITabBarController else {
fatalError("탭바 컨트롤러를 가져올 수 없습니다.")
}
guard let firstVC = sb.instantiateViewController(withIdentifier: TrendViewController.identifier) as? TrendViewController else { return }
let firstNav = UINavigationController(rootViewController: firstVC)
guard let secondVC = sb.instantiateViewController(withIdentifier: RecommendationViewController.identifier) as? RecommendationViewController else { return }
let secondNav = UINavigationController(rootViewController: secondVC)
guard let thirdVC = sb.instantiateViewController(withIdentifier: TheaterViewController.identifier) as? TheaterViewController else { return }
let thirdNav = UINavigationController(rootViewController: thirdVC)
tabBarController.viewControllers = [firstNav, secondVC, thirdNav]
window?.rootViewController = tabBarController
window?.makeKeyAndVisible()
} else {
// 처음 들어왔을 때
let vc = OnboardingViewController()
window?.rootViewController = vc
window?.makeKeyAndVisible()
}
이런 식으로 구현할 수 있더라.
탭바컨트롤러에 viewcontrollers로 넣어서 구현도 가능한 거엿구나........
처음알았다
아씨 저장 안 햇는데 렉먹어서 한 번 날라갓다...........................
무슨 내용을 썼더라... ㅜ.ㅜ
꽤 많이 썼던 거 같은데 하 걍 담부터는 임시저장으루 해서 써놔야겟다
미리 올려두고 수정하는 건 정말 별로군아...
TIL은 왜케 맨날 미뤄지는 건지
뭐 하다 보니까 바뻐서 항상 til을 쓰는 걸 미루는 거 같다
거의 ThisweekILearned와 다를 바가 없음
그치만...... 일단 쓴다는 점에서 의미가 잇다고 생각하여 쓴다.
담 주는 조금 더 부지런히 살아야지
location은 글을 쓰고 잇는데 정리하는 데 시간이 걸려서 코드로 정리하는 부분을 쓸지말지 고민 중이다...
일단 로직 부분은 다 정리햇는데 ㄱ-
아몰라몰라올려~~~~!!!!! 하고 올릴가 싶다가도 그치만 좀 정리해서 올리고 싶기도 하고
또 맵뷰를 보면 이.. 이게머꼬 싶어지기에

웃기다 이모티콘도 있네
암튼!!! ㅠㅠ
아... 내용이 날아가지만않앗어도 진짜 내용 많았는데
너무아쉽다
걍 초반이랑 끝 내용만 남았음............
일주일치 원기옥 이제그만
담주부터 하루이틀에 한번 til 썻던 나날로 돌아간다
아... TMDB TV도 구현하고 Codebase로 customView 짜는 것도 해야 하는데 자꾸 이상한 곳에서 아이거하고싶은데... 하고 멈추게대는거같다
솔직히 TV는 그냥 욕심이라 ㄱㅊ은데 아니 그래도커스텀뷰는한번~~~!!!!
진짜시간이48시간이엇으면좋겠다
암튼지짜자야겟음
'TIL' 카테고리의 다른 글
[SeSAC] August 29, 2023 (2) | 2023.08.30 |
---|---|
[SeSAC] August 28, 2023 (0) | 2023.08.29 |
[SeSAC] August 21, 2023 (2) | 2023.08.22 |
[SeSAC] August 19, 2023 (4) | 2023.08.21 |
[SeSAC] August 15, 2023 (3) | 2023.08.16 |