오늘 오전은 각 팀원들이 맡은 기능 간의 상호작용 없이 바로 담당한 기능을 시뮬레이트하기 위해 TempView
(능력자 팀원이 도와주었다)를 생성하는 initialize 작업을 마친 후 각자의 브랜치들을 갱신하는 작업을 했다. 이 과정에서 Git을 사전캠프 때 충분히 공부해둔 덕분에 팀원들에게 꽤 도움이 될 수 있었다. 드디어 정말로 Xcode 안에서 기능 구현을 시작할 수 있게 되었다. 나에게 가장 큰 문제는, Git은 공부했지만 Xcode의 UIKit/Storyboard 에 대해 아는 것이 거의 없었다는 것이다. 그래서 비교적 맡은 기능이 단순했음에도 불구하고, 모르는 것들 투성이였다. 오늘 안에 결국 기능 구현을 마쳤지만, 그 과정에서 겪은 큰 문제 2개와 작은 문제 1개를 어떻게 해결했는지 정리하고 싶다. 제대로 공부하고 작업한 게 아니다보니, workaround를 찾는 싸움이 되어버린 것 같다.
런치스크린에 AppLogo를 담은 (Image Set의 Asset도 처음 추가해봤다: 이 사이트를 발견해놔서 참 다행이다) UIImageView
를 정렬하고 싶은데, Constraints 아이콘을 눌러도 대다수의 옵션이 비활성화되어있어 추가를 하지 못했다. 결국 비교적 익숙한 코드의 형태로 접근해서 Storyboard 파일을 직접 수정하려고 했는데, 이 과정에서 Storyboard가 XML로 구성되어있는 것을 발견했다.
검색을 통해 다른 Constraints 코드의 예시를 보면서 Storyboard에서 Constraints가 들어가는 코드 Hierarchy를 파악했고, 코드에서 View의 id와 UIImageView의 id를 바꿔넣는 식으로 Constraint를 추가했다.
<constraints>
<constraint firstItem="EVb-wE-3PP" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="centerXConstraintId"/>
<constraint firstItem="EVb-wE-3PP" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="centerYConstraintId"/>
<constraint firstItem="EVb-wE-3PP" firstAttribute="height" relation="lessThanOrEqual" secondItem="Ze5-6b-2t3" secondAttribute="height" multiplier="0.5" id="minHeightConstraintId"/>
<constraint firstItem="EVb-wE-3PP" firstAttribute="width" relation="lessThanOrEqual" secondItem="Ze5-6b-2t3" secondAttribute="width" multiplier="0.5" id="minWidthConstraintId"/>
</constraints>
해당 코드를 저장한 후 Storyboard의 형태로 돌아가 살펴보니, Constraints가 잘 추가된 것을 볼 수 있었다. 사실 위 형태 그대로 넣었던 건 아니고 조금 정제되지 못한 코드를 넣었는데, Storyboard로서 열었을 때 코드가 갱신이 되는 것을 확인할 수 있었다. 이 과정에서 원하지 않는 변화도 있었기에 Attribute와 id등을 수정하는 작업이 필요했다.
이후에 Constraints 추가 UI를 다시 방문하면서, 결국엔 LaunchScreen.storyboard
에서 View
에 대해서 AppLogo를 담은 UIImageView
의 Constraints를 생성하고 싶었는데, UIImageView
만 선택하고 Constraints를 만드려고 하니 옵션이 비활성화 되어있었던 것있음을 알았다. 다만 이번 경험을 통해 Interface Builder (Storyboard)에서 작업하는 게 아닌 XML을 직접 수정하는 방안도 (Constraints에 한하지 않고) 있음을 알게 되었다. 또 한편으로는 Storyboard 그만 만지고 SwiftUI를 얼른 공부해서 써먹고 싶다는 생각이 강하게 들었다.. (UIKit, 너무 불편하다)
IntroductionView.storyboard
에 연결한 IntroductionViewController
에서, 배경 이미지로 설정한 상수 backgroundImage = UIImageView
에 gradientLayer = CAGradientLayer
를 추가해 투명도를 구현하려고 했는데, 이게 생각보다 잘 안되었다.
// 배경 이미지 설정
let backgroundImage = UIImageView(frame: self.view.bounds)
backgroundImage.image = UIImage(named: "IntroductionBackground002.png")
backgroundImage.contentMode = .scaleAspectFill
backgroundImage.clipsToBounds = true
self.view.insertSubview(backgroundImage, at: 0) // UIViewController의 View에 추가
// 그라데이션 레이어 추가
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.view.bounds
// 색상 설정: 아래는 불투명, 위로 갈수록 투명하게 설정
gradientLayer.colors = [
UIColor.black.withAlphaComponent(0.0).cgColor, // 위쪽은 완전 투명
UIColor.black.withAlphaComponent(0.5).cgColor // 아래쪽은 반투명
]
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0) // 시작점 (위쪽)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1) // 끝점 (아래쪽)
self.view.layer.insertSublayer(gradientLayer, above: backgroundImage.layer) // 배경 이미지 위에 그라데이션 추가
코드를 보면 알겠지만 완전히 잘못 짚었다. 그라데이션 레이어를 추가하는 게 아니라 같은 이미지 뷰를 추가해서 alpha 값을 조정하는 방식이 되어야 했다. (ChatGPT의 폐해.. ChatGPT가 맥락을 이해하도록 프롬프트를 적는 게 참 어렵다.)
아래에 적은 코드는 지금 글을 작성하면서 쓴 것이지만 이게 틀렸다고 하더라도 위 코드보다는 엉뚱한 삽질을 덜 했을 것 같다.
// 배경 이미지 설정
let backgroundImage = UIImageView(frame: self.view.bounds)
backgroundImage.image = UIImage(named: "IntroductionBackground002.png")
backgroundImage.contentMode = .scaleAspectFill
backgroundImage.clipsToBounds = true
self.view.insertSubview(backgroundImage, at: 0) // UIViewController의 View에 추가
// 투명도를 조정할 위쪽 이미지 뷰 추가
let transparentImage = UIImageView(frame: self.view.bounds)
transparentImage.image = UIImage(named: "IntroductionBackground002.png")
transparentImage.contentMode = .scaleAspectFill
transparentImage.clipsToBounds = true
transparentImage.alpha = 0.0 // 처음에는 완전 투명
self.view.addSubview(transparentImage)
// 애니메이션으로 투명도 조정 (선택 사항)
UIView.animate(withDuration: 1.0) {
transparentImage.alpha = 1.0 // 애니메이션으로 투명도를 변경
}
아무튼 잘못 짚고서 IntroductionView
가 Modal
형식의 뷰라서 잘 안되나? 등등 삽질을 하다가, 다가오는 프로젝트 마감과 문제의 단순성으로 미루어 보았을 때 코드로 해결하는 건 효율적이지 않다는 생각이 들었다. 물론 계속 매달리면 더 배울 수 있었겠지만, 전에 읽은 Critical Thinking 아티클이 생각나면서 다양한 관점에서 문제를 바라봐야 한다는 생각이 문득 든 것이다. 그래서 나는 바로 포토샵을 켰다. 이미지 어셋으로 넣었던 IntroductionBackground002.png
를 열어서 클리핑 마스크로 투명도를 입히고, 작업하다보니 이미지 자체도 수정이 좀 필요한 것 같아 수정하였다.
결과는 만족스러웠다.
AppIcons
에 대한 팀원 피드백을 반영해 이미지를 갱신했는데(폴더 안의 파일을 통째로 바꿈), 프로그램이 이상하게 변경된 것을 눈치채지 못하는 것 같았다. 원래는 뭔가 수정하면 Commit을 위해 Stage할 거냐고 물어보는데 물어보지 않아서 곤란했다.
어떻게 할지 고민하다가, 그냥 AppIcons
Asset을 임시로 삭제해서 Commit하고, 다시 추가해서 또 Commit했다.
바보같은 문제에는 바보같은 해결방법이 필요하다.. 는 반쯤 농담이고, 임시로 지우는 게 치명적인 게 아니라면 이것도 좋은 workaround인 것 같아 기억해둬야겠다.
오늘은 팀원들이 더 끈끈해진게 느껴졌다. 모르는 건 더욱 적극적으로 물어보고, 서로 알려줬다. 이게 3일만에 이루어질 수 있는 유대감인가? 12시간 이상을 하루마다 함께 한다는 게 어떤 것인지 체감이 된다. 덕분에 나도 내가 배웠던 Git을 팀원에게 더욱 알려주면서 좋은 공부를 할 수 있었다. 반대로 내가 모르는 주제에 대해서 평소에 잘 물어보시지 않는 분이 나에게 도움을 요청했는데, 물어봐줘서 고맙다는 감정과 동시에 잘 모르니 미안한 감정이 들었다. 괜히 알량한 자존심으로 버틸 생각은 일찌감치 버리고, 익살스럽게 잘 모른다고 했다. 다행히 옆에 있던 팀원분들이 자연스럽게 도와주셨고, 나는 그 질문 과정에서 질문해주신 팀원분이 받고 싶은 답변을 들을 수 있도록 옆에서 조금만 보조했다. 팀원끼리 웃을 수 있는 분위기란 참 좋다. 역시 위트는 소통의 윤활제다 :)
어쩌다보니 본 글이 내일배움캠프 우수 TIL로 선정되었다.. 기쁘면서도 한편으로는 너무 부끄럽다 😅
하지만 덕분에 관심을 받아서 튜터님으로부터 좋은 피드백을 받거나, 다른 팀원으로부터 유용한 방법을 배울 수 있었다:
스토리보드는 UIKit을 사용하는 방법 중 하나일 뿐, UIKit == 스토리보드
로 동일시 되지는 않는다고 한다. (당시에만 해도 나는 같은 건 줄 알았다) 막연하게 최신 버전이라고 여겼던 SwiftUI는 현재까지도 한창 발전 중인 프레임워크로, 복잡한 뷰 커스텀의 경우 SwiftUI만으로는 힘들어 아직도 UIKit을 래핑해서 사용하기도 한다고 한다. UIKit과 SwiftUI는 상호보완적이고, 사용 비율도 UIKit이 압도적이다. SwiftUI가 우대사항에 있더라도, 레거시 코드들은 UIKit으로 이루어져있기 때문에 두 개의 프레임워크를 사용하는 것은 결국 순서상의 차이일 뿐 같다고 한다. 튜터님 유익한 피드백 감사합니다 :)
팀원 분이 Constraints를 추가하는 또다른 방법을 하나 알려주셨다. 왼쪽에 있는 구성요소 목록에서 Constraints를 넣고 싶은 요소를 우클릭한 채로 드래그하면 Constraints를 추가할 수 있는 윈도우가 나온다. 또한 어떤 요소에 대한 Constraints를 넣고 싶으면, Constraints를 적용하고 싶은 요소에서 기준으로 삼을 요소에 우클릭한 채로 드래그하면 된다. 알려주셔서 감사합니다!! 엄청 직관적이에요!
우와 덕분에 App Icon Generator라는 유용한 사이트 알아갑니다!