기존 코드
func roundButton() {
personalButtons.forEach {
$0.layer.cornerRadius = min($0.bounds.height, $0.bounds.widht) / 2
$0.clipsToBounds = true
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSUbviews()
roundButton()
}
→ 함수를 통해 viewDidLayoutSubviews() 시점에 버튼을 원형으로 만듦
💡 피드백
viewDidLayoutSubviews() 시점이라도 오토레이아웃이 정확히 잡히지 않는 경우도 간혹 있음layoutSubviews()를 사용하면 크기가 확정된 시점에 원형으로 만들 수 있으므로 더 정확할 것변경 코드
class CircularButton: UIButton {
private var aspectConstraint: NSLayoutConstraint?
init() {
super.init(frame: .zero)
setConstraints()
}
required init?(coder: NSCoder) { ... }
// 동그란 원을 만들기 위한 제약
private func setConstraints() {
translatesAutoresizingMAskIntoConstraints = false
asepctConstraint = heightAnchor.constraint(equalTo: widthAnchor)
aspectConstraint?.priority = .required
asepctConstraint?.isActive = true
}
// 원형으로 자르기
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = min(bounds.height, bounds.width) / 2
clipsToBounds = true
}
}
// override func viewDidLayoutSubviews() 삭제
→ 커스텀 버튼 CircularButton을 생성하여 원형 버튼 생성
⚠️ 버튼이 타원형으로 그려지는 문제 발생

❗️원인: 오토레이아웃 충돌
CircularButton.heightAnchor.constraint(equalTo: widthAnchor)
buttonStack.widthAnchor.constraint(equalTo: view.safeAreaLayoutGuide.widthAnchor, constant: -30)
rowButtonStack.distribution = .fillEquarly
// firstButtonStack, secondButtonStack
// rowButtonStack 넓이 = (buttonStack.width - spacing) / 2
→ rowButtonStack.distribution에 따라 CircularButton.width = (buttonStack.width - spacing) / 2가 되어야 함
→ 원형 버튼의 넓이 = 높이 조건도 맞춰야 함
⇒ 즉, CircularButton.height = (buttonStack.width - spacing) / 2가 되어야하는데, buttonStack.height는 2개의 원형 버튼 높이만큼 충분하지 않음
➡︎ 넓이 조건만 맞추고 높이는 부족한 채로 버튼이 생성
✅ 해결 방법
1️⃣ 버튼 높이 제한하기
func setUI() {
...
NSLayoutConstraint.active([
...
])
personalButtons.forEach {
$0.widthAnchor.constraint(lessThanOrEqualTo: firstButtonStack.heightAnchor).isActive = true
}
}
→ 원형 버튼의 높이를 rowButtonStack과 동일하거나 더 작도록 맞춤

➡︎ 부족한 높이만큼 가로 길이가 줄어들어 버튼의 전체적인 크기는 작아졌지만 동그란 원형 버튼 생성 가능
2️⃣ rowButtonStack.distribution 변경하기
firstButtonStack.distribution = .equalSpacing
secondButtonStack.distribution = .equalSpacing
→ 두 스택의 분배를 .fillEqually가 아닌 .equalSpacing으로 변경
→ .equalSpacing : 스택 뷰 객체 사이를 동일한 빈 공간으로 채움

➡︎ 원형 버튼이 rowButtonStack 넓이만큼 늘어나지 않고, 원형 버튼의 넓이 = 높이을 충족하는 범위에서 생성됨
➡︎ rowButtonStack의 높이 제한 함수가 없어도 1️⃣과 같은 형태의 버튼 생성 가능
3️⃣ buttonStack.width를 객체가 있는 범위만큼만 잡기
func setUI() {
...
NSLayoutConstraint.activate ([
...
buttonStack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
// buttonStack.widthAnchor.constraint(equalTo: view.safeAreLayoutGuide.widthAnchor, constant: -30),
buttonStack.leadingAnchor.constraint(greaterThanOrEqualTo: view.safeAreLayoutGuide.leadingAnchor, constant: 15)
buttonStack.trailingAnchor.constraint(lessThanOrEqualTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -15)
])
}
→ 위 코드를 제외한 나머지는 기존 최초 코드 유지
→ buttonStack의 leadingAnchor와 trailingAnchor를 greaterThanOrEqualTo, lessThanOrEqualTo로 맞춤으로써 buttonStack.width를 가변으로 만듦
→ 원형 버튼의 넓이 = 높이을 충족하는 범위에서 버튼들이 생성됨
→ rowStackButton 생성시 설정한 .spacing = 10을 제외하고는 스택뷰 내 객체 간 공백 없음

➡︎ 원형 버튼들이 1️⃣, 2️⃣ 방법과 달리 중앙에 모여있음
SFSafariViewController
: Safari 앱과 거의 동일한 기능 제공
: Safari의 쿠키, 사용자 로그인 정보 등을 앱과 공유 (단, 읽기만 가능. 앱에서 사용은 불가능)
: 앱과 Safari 간의 데이터 공유와 같은 상호작용 불가능
: 커스텀 불가능
WebView
: 앱의 UI에 웹의 콘텐츠를 통합
: url 대신 HTML, CSS, JavaScript 콘텐츠르 만들어 사용 가능
: 필요한 기능들을 개발자가 직접 상세히 구현 가능 - UI 커스텀 등
: 앱과 웹의 상호작용 가능
→ 간단하게 블로그 페이지만 보여주는 이번 프로젝트에서는 SFSafriViewController를 사용하는 것이 적합했다고 판단한다.
참고: [Swift/TIL #36] Web 보여주기 (Safri 앱, SFSafariViewController, WKWebView
참고: [iOS/Swift] WKWebView (+ SFSafariViewController)
.first(where:)기존 코드
@objc private func navigateTo(_ sender: UIButton) {
guard let name = sender.currentTitle else { return }
let vc = persons.filter { $0.name == name } [0].vc
navigationController?.pushViewController(vc, animated: true)
}
→ persons.filter { $0.name == name }[0] 이 빈 배열일 경우와 같이 인덱스 조회 오류가 날 가능성 있음
⇒ 해당 프로젝트에서는 persons.name으로 버튼의 title을 생성했기 때문에 오류가 나지 않으리라 판단하고 위험 부담이 있는 코드 그대로 사용
💡 피드백
.first(where:) 메소드로 가독성도 높이고 오류 방지 가능.filter 메소드는 필터링하기 위해 모든 요소를 순회함.first(where:)는 첫 요소를 찾으면 종료되므로 성능 면에서도 효율적변경 코드
@objc private func navigateTo(_ sender: UIButton) {
guard let name = sender.currentTitle else { return }
if let person = persons.first(where: { $0.name == name }) {
navigationController?.pushViewController(person.vc, animated: true)
}
}
→ .first(where:) 코드 적용
기존 코드
final class PetCardViewController: UIViewController {
private var isShadowPathSet = false
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guard !isShadowPathSet else { return }
shadowView.layer.shadowPath = UIBezierPath(roundedRect: shadowView.bounds, cornerRadius: shadowView.layer.cornerRadius).cgPath
isShadowPathSet = true
}
...
}
→ isShadowPathSet 변수를 통해 shadowPath를 1번만 설정하도록 함
💡 피드백
viewDidLayoutSubviews()가 많이 호출되는 함수가 아니므로 shadowPath를 1번만 설정하도록 하지 않아도 성능에는 큰 차이 없음
shadowPath 설정을 최초 시점으로만 제한할 경우, 화면 방향이 바뀐다거나 하는 이벤트 발생 시 올바른 shadowPath를 설정할 수 없음
→ shadowPath를 사용하는 목적인 캐싱이 불가능
➡︎ 추후 프로젝트 진행 시 참고!