오늘 오전은 각 팀원들이 맡은 기능 간의 상호작용 없이 바로 담당한 기능을 시뮬레이트하기 위해 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라는 유용한 사이트 알아갑니다!