(먼저 만들어 본 거 뿌듯해서 만족한 상태)
새로운 주차가 시작되었다. 지금까지는 프로그래밍 언어인 Swift
의 문법을 학습했다면, 오늘부터는 iOS 앱 개발
을 학습한다. 문법 기초 주차 때 과제가 playground
를 통해 사칙연산 기능을 구현하는 것이었는데, 그 연산 기능을 수행하는 계산기 앱을 UIKit
을 통해 구현하는 것이 이번 과제로 주어졌다. 나는 문법 주차 때 과제 외 개인적인 도전으로 앱 프로젝트를 같이 시작했었는데, 그게 이렇게 돌아올 줄은 몰랐다. 이미 만들어놓은 프로젝트의 틀을 이번 과제로 옮겨올 수 있게 됐다. 다만 과제에서 제시한 구현 사항을 지키느라 레이아웃의 대부분을 수정했다.
[UI]
- 컴포넌트 :
UILabel
,UIButton
,UIStackView
사용AutoLayout
적용UIButton
동작 방식 :addTarget
UIKit
으로 구현(storyboard
또는code base
선택, 둘 다 하면 좋음)
[Logic]
Int
계산만 구현=
버튼을 눌렀을 때만 연산이 이루어지도록 구현- 연산은
Swift
의 기본 제공 메소드 활용
숫자 버튼과 연산자 버튼을 눌렀을 때 입력 내용이 띄워지는 텍스트 구간이다.
// main view controller에 컴포넌트 선언
private lazy var inputLabel: UILabel = {
let label = UILabel()
label.backgroundColor = .black
label.textColor = .white
label.textAlignment = .right
label.font = .systemFont(ofSize: 60, weight: .bold)
return label
}()
// 레이아웃 지정 함수
private func layout() {
let superView = view.safeAreaLayoutGuide
let offset: CGFloat = 30.0
NSLayoutConstraint.activate([
inputLabel.leadingAnchor.constraint(equalTo: superView.leadingAnchor, constant: offset),
inputLabel.trailingAnchor.constraint(equalTo: superView.trailingAnchor, constant: -offset),
inputLabel.topAnchor.constraint(equalTo: superView.topAnchor, constant: 200),
inputLabel.heightAnchor.constraint(equalToConstant: 100),
])
}
버튼 정보를 갖고 있는 Model
을 정의했다.
struct ButtonInfo {
let role: ButtonRole
let name: ButtonName
}
// butto role은 숫자와 숫자가 아닌 버튼의 구분, button name은 각 버튼의 오리지널리티를 나타낸다. 내용 코드 생략.
enum ButtonRole {}
enum ButtonName {}
UIButton
을 상속하는 커스텀 클래스를 정의했다. ButtonInfo
를 이니셜라이징 하도록 하여 buttonInfo.role
, buttonInfo.name
에 따라 버튼 color
, title
, image
가 지정되도록 구현했다.
class Button: UIButton {
let buttonInfo: ButtonInfo
init(buttonInfo: ButtonInfo) {
self.buttonInfo = buttonInfo
super.init(frame: .zero)
layout()
}
private func layout() {
// 과제에서 지정한 button layout 지정 //
}
}
[!]
과제에서는 곱하기, 나누기 버튼 모양이 *, /
지만 나는 ×, ÷
모양을 넣고 싶기 때문에 SF Symbols
를 활용하기로 했다. UIImage(systemName: "")
에 심볼 이름을 넣으면 애플에서 제공하는 이미지를 버튼에 넣을 수 있다.
private func setButton() {
switch buttonInfo.name.withImage {
case true:
setImage(buttonInfo.name.systemName)
case false:
setTitle(buttonInfo.name.title)
}
}
ButtonName
enum
에 withImage
라는 Bool
타입 연산 프로퍼티를 정의하여 이미지 넣을 버튼을 구분하도록 구현했다.
4개의 버튼이 수평으로 들어가는 horizontalStackView
와, 그 horizontalStackView
가 수직으로 4개 들어가는 verticalStackView
를 만들었다. 그렇게 총 16개
의 버튼이 들어가는 것이다.
class HorizontalStackView: UIStackView {
init() {
super.init(frame: .zero)
layout()
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func layout() {
axis = .horizontal
spacing = 10
distribution = .fillEqually
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: 80).isActive = true
}
// 외부(verticalStackView)에서 호출하여 button을 연속적으로 sub view에 추가하는 함수
func addSubviews(_ buttons: [Button]) {
buttons.forEach {
addArrangedSubview($0)
}
}
}
class VerticalStackView: UIStackView {
// horizontal stack view를 4개 정의하였다.
private lazy var firstHStack: HorizontalStackView = {
let stackView = HorizontalStackView()
let sevenButton = Button(buttonInfo: .init(role: .number, name: .seven))
let eightButton = Button(buttonInfo: .init(role: .number, name: .eight))
let nineButton = Button(buttonInfo: .init(role: .number, name: .nine))
let addButton = Button(buttonInfo: .init(role: .operation, name: .add))
stackView.addSubviews([sevenButton, eightButton, nineButton, addButton])
return stackView
}()
// ... //
// 정의한 4개의 hstack을 sub view로 추가
private func addSubviews() {
[firstHStack, secondHStack, thirdHStack, fourthHStack]
.forEach {
addArrangedSubview($0)
}
}
}
// main view controller에서 vstack을 컴포넌트로 선언
private lazy var buttonView = VerticalStackView()
과제 요구사항에서 지정해준 숫자 버튼과 연산자 버튼의 색깔을 UIColor
를 extension
하여 타입 프로퍼티로 정의하였다.
extension UIColor {
static let numbersButtonColor = UIColor(red: 58/255, green: 58/255, blue: 58/255, alpha: 1.0)
static let operatorButtonColor = UIColor.systemOrange
}
이렇게 해놓으면, Button
클래스에서 role
에 따라 backgroundColor
를 지정할 때 내가 정의한 이름으로 넣으면 된다.
private func setColor() {
switch buttonInfo.role {
case .number:
backgroundColor = .numbersButtonColor
case .operation:
backgroundColor = .operatorButtonColor
}
}
내 프로젝트와 많이 다르다. 버튼이 더 적기도 하고, 나는 StackView
를 적용하지 않았었는데 이번 기회에 적용해보게 됐다. 그냥 UIView
안에 버튼 여러개를 때려넣는 것보다 코드가 훨씬 줄었다.