그래 레벨 1 까진 강의에서 배운대로 했더니 괜찮았다.
근데 레벨 2 부터 UIStackView
를 쓰라고 할 줄은 몰랐지ㅜ
우선 스택뷰는 총 4개가 필요할 것 같고 저 4개를 한번에 또 묶어주는 작업도 필요할 것 같다.
그냥 buttons
라는 메서드를 만들어서 그 안에 버튼의 사이즈
, 컬러
, 원형크기
까지 모두 조정했다.
여기가 좀 어려웠는데,
그냥 titleLable.font =
으로 쓰면 계속 오류가 났다.
오류 메세지를 보면 뒤에 ?
를 붙이라고 해서 그 뒤로 오류가 사라졌지만 왜 ?
가 붙나 의아했다.
알고보니 titleLabel?
은 titleLabel
이 존재 할 경우에만 font
속성을 변경하겠다는 의미였다.
만약 버튼에 titleLabel
이 없는 상황이 생긴다면 titleLabel?
은 nil
이 될 것이고,
그러면 font
설정이 무시될 것이기 때문이다.
이런 옵셔널 체이닝을 통해서 titleLabel
이 존재하는 경우에만 font
를 설정하게 될 것이다.
.
.
그리고 스택뷰 메서드도 하나 더 만들었는데 과제에서
힌트 : func makeHorizontalStackView(_ views: [UIView]) → UIStackView 와 같은 형태로 horizontalStackView 를 생성하는 메서드를 정의해두면 좋습니다. 똑같은 스택뷰 4줄이 필요하기 때문이죠.
이런 힌트를 주어서 힌트대로 만들었다.
똑같은 스택뷰 4줄이 필요한데 그때 그때마다 지정하고 있으면 꽤나 코드가 길어졌겠구나 싶더라.
아무튼 저 버튼들과 스택뷰로 이제 쌓아가면 되겠다.
.
.
7
,8
,9
,+
로 만들었던 수식을 다 복붙하고 숫자랑 이름만 모두 변경했으니 이제 잘 되거라고 생각했는데..
7
,8
,9
가 없고 맨 마지막 줄이 올라와있었다.
뭔가 스택뷰가 순차적으로 내려가는게 아니라 위로 쌓였나? 싶어서 다시 코드를 봤다.
스택 븅의 Top이 다음 만들어지는 스택 뷰에 맞춰야했는데 다 라벨에 맞춰둬서 그랬었다.
사실 이런거 블로그 쓰는 것도 좀 창피한데, 나 같은 사람 어딘가에는 있겠지...
아무튼 저 label
을 각각 바로 위에있는 스택뷰 이름으로 바꿔주면 순차적으로 내려올 것이다.
내려는 왔는데 애들이 사이가 안 좋다.
아주 그냥 멀찍이 떨어져있으려고 한다.
아까 내가 스택 뷰의 간격만 조정하고는 ....
첫번째 스택뷰에 라벨과의 offset
거리도 다 복붙 되었었는데 그걸 수정하지 않았기 때문이다. ㅜ
첫번째 스택뷰는 라벨과 거리가 넓기 때문에 보면 다 라벨과의 간격으로 떨어져 있는 걸 볼 수 있다.
stackView.spacing = 10
으로 주었으니, offset
도 10
으로 설정해주면 간격이 딱 딱 맞을 듯 하다.
아 굿~!
진도 잘 나가고 있다~!
그럼 여기서 추가로 할 일은?
UIStackView
를 사용해서 세로 스택 뷰 생성하는 일이다.
다행히 과제에서
이렇게 알려주었기 때문에 속성 값은 알 수 있었다.
여기서 아까 스택뷰 메서드 만들 때도 썼는데,
코드에서 arrangedSubviews
라는 게 보인다.
.
.
.
arrangedSubviews
는 UIStackView
에 포함된 뷰들을 배열로 나타낸 속성인데,
스택 뷰가 이 배열에 있는 뷰들을 관리하고 배치해준다고 생각하면 될 것 같다.
UIStackVie
를 만들 때 arrangedSubviews
에 원하는 하위 뷰들을 넣어주면,
이 뷰들이 스택 뷰의 방향 (축 : axis)
과 설정에 따라 자동으로 배치가 된다.
axis
속성은 UIStackView
안에 있는 뷰들이
수평(horizonal)
또는 수직(vertical)
방향으로 배치될지 정해주는 것이다.
위 코드에서는 [firstStackView, secondStackView, thirdStackView, lastStackView]
를
arrangedSubviews
로 전달하고,
네 개의 수평 스택 뷰를 mainStackView
안에 수직으로 배치하도록 설정한 것.
.
.
.
과제 내용에서 distribution = .fillEqually
라는 내용이 있어서 그대로 했다.
이 distribution
은 뭘까. 뭐길래 fillEqually
로 설정을 한 걸까.
distribution
의 속성은 스택뷰 내에서 뷰들이 얼마나 공간을 차지할지,
그리고 각 뷰들의 크기 분포를 어떻게 할 것인지를 정의한다고 한다.
이렇게 보면 옵션이 생각보다 여러 개가 나오는데 옵션만 간단히 정리해봤다.
fill
각 뷰가 자신의 크기를 사용하여 공간을 채우도록 한다. 이 옵션은 각 뷰가 자신의 콘텐츠 크기만큼만 공간을 차지하게 된다.
fillEqually
모든 뷰가 동일한 크기로 공간을 균등하게 나누도록 하고, 각 뷰는 스택 뷰의 전체 공간을 동일하게 분할한다. 쉽게 말하자면 모든 뷰가 동일한 크기를 가지고 뷰 크기와 관계없이 균등하게 배치된다는 뜻.
fillProportionally
각 뷰는 자신의 콘텐츠 크기에 비례하여 공간을 차지하는데, 크기가 더 큰 뷰는 더 많은 공간을 차지하고, 크기가 작은 뷰는 상대적으로 적은 공간을 차지한다.
equalSpacing
뷰들 사이에 동일한 간격을 유지하도록 설정 한다. 뷰들 사이의 간격이 같고, 뷰들은 그 크기에 따라 공간을 차지하게 된다.
equalCentering
뷰들의 중앙에 동일한 간격을 배치하도록 설정한다. 뷰들이 중앙에 위치하고, 뷰들 사이의 간격만 균등하다. 그래서 뷰의 크기는 각 뷰의 콘텐츠 크기에 따라 달라질 수 있습니다.
이걸 보고는 그럼 나는 스택 뷰들이 어차피 크기가 다 동일하니까
equalCentering
으로 해도 되겠다싶어서 해보니 fillEqually
과 동일하게 잘 됐다.
하지만 앞서 설명한 distribution
의 속성들을 파악하고 상황에 맞게 사용하는 것이 맞으니
fillEqually
로 다시 도르마무!
.
.
.
과제 내용에서 spacing
을 10
을 주라고 나와있었다.
그럼 자동으로 스택뷰들간의 간격이 10
만큼 벌려지는 것인데,
나는 그 전에 미리 스택뷰마다 바로 위 스택뷰와의 거리를 offset
10
으로 주고 있었다.
그럼 이제 mainStackView
가 알아서 스택뷰들의 간격을 10
만큼 조정해줄테니 적을 필요가 없을 것 같다.
역시나 offset
에 있던 각 스택뷰를 벌려놓았던 값들을 지워도
mainStackView
의 spacing
덕에 간격이 그대로 잘 유지되고 있다.
버튼색상을 변경하려면,
우선 버튼 메서드에 백그라운드 컬러를 적용할 수 있는 매개변수를 추가해줘야한다.
그리고 매개변수를 변경하게 되면,
함수 호출하는 코드도 수정을 해줘야한다.
수정 전에는 오른쪽에 난리가 났다.
예전에 한 수강생분이 알려줬는데
컨트롤
+ 시프트
+ 클릭
으로 수정 원하는 부분 선택하면 한 번에 수정 가능하다.
그걸로 했더니 백그라운드 추가하는건 정말 편했다.
연산자들만 orange
색으로 변경해주면!!!!
아 왜 그대로신가요.
아 이미 내가 다크 그레이로 지정을 해놨구나................
버튼 메서드 수정하는 걸 깜빡했다.
button.backgroundColor = backgroundColor
로 지정해주니,
원하는대로 잘 나온걸 확인했다.
과제 Lv.5는 버튼을 원형으로 만들기인데,
이미 버튼 메서드를 만들면서 해 놓았던 터라 바로 액션을 추가해보려고 한다.
번호를 눌렀을 때 번호가 label
에 보이게 하고,
각 버튼을 눌렀을 때 어떤 버튼이 눌렸다 라는 프린트를 추가하려한다.
button.addTarget(self, action: action, for: .touchUpInside)
을 버튼 메서드에 추가하면,
touchUpInside
로 했기 때문에 버튼을 눌렀다가 손을 뗐을 때
특정 이벤트를 실행하게 만들 것이다.
그리고 버튼 액션에 오류가 난 건
매개 변수에 추가를 안 해줘서 그랬던거니, 추가해주면 오류가 사라진다.
자 그럼 각 버튼들에 아까 백그라운드를 추가해준 것처럼
action: #Selector(buttonTapped)
를 추가해주면 된다.
(나도 이런거 써본다고 자랑 해보고 싶었다.)
.
.
.
.
그리고 하단에 액션 내용을 적었다.
버튼을 눌렀을 때 해당 버튼의 번호를 출력 되게끔 말이다.
버튼을 탭 했을 때 눌렸는지 확인하고자 함이다.
그리고 @objc
는 Objective-C
와의 호환을 위해 사용하는건데,
addTarget
메서드에서 사용하는 Selector
는 Objective-C
방식이기 때문에
@objc
키워드가 필요하다고 한다.
뭔가 둘이 짝짝꿍 느낌?
private
로 viewController
내부에서만 접근할 수 있도록 제한을 해주었다.
근데 노랭이와 피가 동시에 났다.
노랭이는 Xcode에서 수정을 권하는 사항이라고 한다.
일단 피가 난 부분을 먼저 보니 Xcode
가 자기가 고쳐준다면서 Fix
를 눌렀는데
언더스코어가 매개변수 이름으로 나왔다.
수정 전 오류 내용은 "이름 없는 매개 변수는 빈 이름 ''으로 작성해야 합니다" 인데
어떤 이름을 지정해줘야 했던건가..?
어떤 이름인지 몰라서 일단 언더스코어로 뒀다.
알아보니 '_'
로 하게 되면 매개변수 이름을 사용하지 않아도 되는 경우에 저렇게 적용한다고 한다.
그렇게 한번 빌드해보고 버튼을 눌러보니 nil 버튼 클릭 됨
이라고 한다.
왜 타이틀을 가져오지 못해. 왜.
그렇게 수강생 동기에게 쪼르르 달려가 물어봤다.
.
.
.
나 : 이거 왜 안되는지 알아여..?
동기 : "sender.title(for: .nomal) 하고 옵셔널 바인딩 해보세여"
머야? 진짜 되잔ㅇ하?
저 sender
가 뭔데!!!!!!
.
.
.
.
sender
는 버튼을 눌렀을 때 이벤트를 발생시킨 객체를 가리키는 매개변수라고 한다.
여기서는 각 버튼이 sender
로 전달되는 것이고..
이 함수가 UIButton
객체를 눌렀을 때 호출되도록 설정되어 있다고 한다.
그리고 sender
를 쓰는 이유는 이 메서드가 어떤 버튼에 의해 호출되었는지 알기 위함 이라고 한다.
그러니까 내가 자꾸 오류가 나고 피가 나고 노랭이가 터진 이유가.
함수 정의에서 매개변수 이름을 정확히 지정해주지 않아서 생긴 오류였다.
우리 깐깐쟁이 Swift
는 함수를 정의할 때 매개변수 이름을 정확히 지정해야 한다고 한다.
지금처럼 buttonTapped
함수는 버튼 이벤트가 발생했을 때 호출되기 때문에
어떤 버튼이 이벤트를 발생시켰는지 전달하기 위한 sender
매개변수를 포함해야 했던 것이다.
하지만 만약 함수 정의에서 매개변수 이름 sender
를 빼거나 이름 없이 작성하면
깐깐쟁이 Swift
가 어떤 매개변수를 참조해야 할지 알 수 없으니 오류가 발생해버리는 것.
.
.
.
아무튼 이렇게 액션버튼마다 프린트 출력을 해주고,
초기화 버튼은 눌렸을 때 0
이 될 수 있게 해놨다.
그리고 각 버튼들에게 action: #selector
값을 각 연산자나 숫자에 맞게 지정해주고..
실행했다.
좋았어...
이렇게 Lv.5 까지의 길고 긴 여정을 적어봤다.
오늘 인터페이스와 이벤트를 효율적으로 구성하는 데 중요한 개념들을 많이 알게 됐다.
처음에는 각 버튼을 개별적으로 설정하고,
그 후 스택 뷰를 사용해 버튼들을 묶어서 좀 더 효율적으로 배치할 수 있었다.
SnapKit을 활용하여 AutoLayout을 코드로 작성하는 부분도 계속 하다보면
스토리보드보다 더 편해질 것 같다 (진짜?)
아 그리고 UIStackView
를 사용해 뷰를 구성할 때
arrangedSubviews
로 서브뷰를 추가하고 여러 속성으로 레이아웃을 조정할 수 있다는 것도 알게 됐고,
button.addTarget(_:action:for:)
메서드를 이용해서 버튼 클릭 이벤트를 처리하는 것도 하다보니 적응이 되었다.
후에 모든 버튼이 동일한 메서드로 연결되어 있을 때,
각각 어떤 버튼인지 구분할 수 있도록 sender
매개변수를 사용하는 방법도 알게됐다. (고마워요 소림사)
본 내용에는 없지만 argument label
에 대해도 듣게 됐었는데,
매개변수 레이블을 명확히 지정하고 싶다면,
sender
앞에 'withSender' 와 같은 이름을 적어주면 된다고 한다.
그러면 더 직관적으로 코드의 의도를 전달 할 수 있는 장점이 있다고 ...!
앞으로도 매개변수 이름과 레이블의 차이를 유념해야할 듯 싶다.
그리고 정말 이렇게 TIL을 쓰니,
매일 배운 내용을 기록하며 어떤 부분을 더 연습해야 할지 파악할 수 있어서
학습에 도움이 정말 많이 된다. 좋다.
다음 편을 기대해주세요.
사이 안 좋던 스택뷰들이 친해져서 보기 좋네요^^ 앞으로도 화목한 UI 기대할게요~