안녕하세요. 스파르타코딩클럽 내일배움캠프 iOS 트랙에 참여중인 에밀리입니다. tistory
에서 velog
로 옮겨와 첫 포스팅이네요. (존댓말 끝)
지난 주는 캠프 온보딩 주차로 팀 소개 앱을 만드는 미니프로젝트 과제를 수행했고, 이제 2주차가 되었다.
이번 주는 프로그래밍 기초 주차로서 영상강의를 통해 Swift의 기본 문법을 공부하고, 과제로는 계산기 앱 만들기라는 개인 프로젝트가 주어졌다.
나는 기본 데이터 타입부터 연산자와 함수 부분까지 강의를 빠르게 훑고 바로 과제에 돌입했다.
과제 가이드에는 playground에 구현하라고 되어있지만 나는 개인적인 도전으로 app도 제작하려 한다. 따라서, playground와 Xcode project를 병행한다. view는 storyboard가 없는 code base UIKit으로 구현할 예정이다.
- playground 파일을 생성하기
- Calculator 클래스를 만들기
- class에서 더하기, 빼기, 나누기, 곱하기 연산 수행
- 생성한 클래스를 이용하여 연산을 진행하고 결과를 출력
- 오류가 날 수 있는 '예외처리' 상황에 대해 고민해보기
1 ~ 4번 까지는 무난하게 구현했으나 5번의 예외처리 상황이 좀처럼 떠오르지 않았다. 가이드에 구현을 할 수 없어도 괜찮아요!
라고 써있길래 아하! 그러면 나는 구현 안해야지.
라고 생각하고 있었다.
그러다가 다른 분들과(우리 팀 아님) 과제 얘기를 나눴는데, 예외처리가 발생할 수 있는 경우에 대한 아이디어를 얻었다. 바로 operation
파라미터를 enum
이 아닌 String
으로 받는 거였다. 그렇게 되면 operation
파라미터에 +
, -
, *
, /
외 다른 문자가 입력될 경우 처리가 필요한 것이다.
하지만 나는 String
으로 처리하고 싶지 않았다. enum
으로 하고 싶었다. enum
으로 파라미터를 받으면 4가지의 연산 외의 다른 경우, 즉 예외 상황이 아예 발생하지 않는다. 그래서 여전히 예외처리는 skip하자는 결론을 내렸다. 다른 말로 하자면 예외 발생 가능성을 없앰으로써 예외처리를 한 것이다.
- 프로젝트를 생성한다.
- Main.storyboard 파일을 과감하게 삭제한다.
- Info.plist와 project Info(Xcode navigator 맨 위 app 이름을 누르면 나오는 화면에 있다. 이 작업을 다 마친 뒤 포스팅을 작성중이므로 캡쳐는 생략)에서 main storyboard 관련 항목을 다 지운다.
- initial view를 구성할 MainViewController 파일을 생성한다. (보통 프로젝트를 생성하면 기본적으로 있는 ViewController를 refactor하여 사용함)
- SceneDelegate에서 root view 지정 코드를 작성한다. (아래)
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else { return }
window = UIWindow(windowScene: windowScene)
window?.backgroundColor = .systemBackground
window?.rootViewController = MainViewController()
window?.makeKeyAndVisible()
}
위 캡쳐는 code base 개발을 위한 나의 기본 세팅 화면이다.
여기까지 하고 실행했을 때, Simulator
에도 Preview
와 같은 화면이 잘 나온다.
이 때, 저 코드에서 lazy var
란 무엇인가와 addSubview()
함수와 layout()
함수 속 코드들의 의미가 궁금하신 분들이 있을 수 있다. 사실 나도 지금 타이핑 하는 시점에 바로 술술 설명할 정도로 이해하고 있는 건 아니므로, 다음에 정리하도록 한다. (하게 된다면.)
이건 다른 사람들과 다른 나만의 학습 태도일 수 있는데, 나는 실습 강의를 들을 때 모든 코드의 근본적 정의와 원리를 이해하고 넘어가기 보다는 코드의 동작을 인식하며 따라가는 편이다. 지금까지 독학을 하며 그렇게 이론보다는 실습 위주로 학습해왔다. 그래서 저 코드를 치면 저렇게 화면에 나온다는 걸 나는 알고 있다.
하지만 지금 문득, 그래서 이론적인 부분이 나의 취약점이겠다는 생각이 든다. 개념을 정리하는 습관을 들여 부족한 부분을 채워야 겠다. 근데 지금은 안한다. 앱부터 만들 거다.
아이폰 계산기 앱 캡쳐화면. 이걸 참고하여 UI를 구성할 것이다.
Label
2개 필요 : 최근 결과값 label,=
눌렀을 때 위에 생기게 될 연산 내용 labelButton
19개 필요 (맨 좌측 하단 계산기 모양 버튼은 생략 예정)- (추가 기능) AC button의 경우 입력을 시작했을 때 backspace button으로 바뀐다. 이번 주 내로 시간이 허락한다면 구현하려 한다. 기본적으로는 AC button을 입력내용 reset으로 구현한다.
내가 오늘까지 구성한 화면
- processLabel : 맨 위에 있는 흐리고 작은 텍스트 영역. 원래
isHidden
이true
다. input을 입력하고=
버튼을 눌러야 나타나며, 연산 내용을 표시한다.- resultLabel : input이 입력되는 텍스트이자
=
버튼 tap 시 결과값을 표시하는 영역.- ButtonArea :
UIView
컴포넌트로서, 2개의 label 아래 공간을 차지한다. 내부 컴포넌트로UIButton
을 여러개 가질 것이다.- button :
UIButton
컴포넌트로서,setTitle
과addAction
을 외부(ButtonArea
)에서 호출해 버튼에 따라 다른 title과 동작을 갖도록 한다.
// Button
class Button: UIButton {
override init(frame: CGRect) {
super.init(frame: .zero)
layout()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func layout() {
// 버튼 배경색, 모양
backgroundColor = .darkGray
layer.cornerRadius = 45
// 버튼 text 폰트 지정
titleLabel?.font = .systemFont(ofSize: 40)
setTitleColor(.white, for: .normal)
setTitleColor(.lightGray, for: .highlighted)
// 버튼 크기
widthAnchor.constraint(equalToConstant: 90).isActive = true
heightAnchor.constraint(equalToConstant: 90).isActive = true
}
// 외부에서 button을 선언할 때, 텍스트와 동작 custom
func setTitle(_ title: String) {
setTitle(title, for: .normal)
}
func addAction(_ title: String) {
let buttonTapped = UIAction { _ in
print("\(title) button tapped")
}
addAction(buttonTapped, for: .touchUpInside)
}
}
// Button을 ButtonArea에서 호출
class ButtonArea: UIView {
private var testButton: Button = {
let button = Button()
button.setTitle("AC")
button.addAction("AC")
return button
}()
private var testButton2: Button = {
let button = Button()
button.setTitle("+/-")
button.addAction("+/-")
return button
}()
private var testButton3: Button = {
let button = Button()
button.setTitle("%")
button.addAction("%")
return button
}()
// ... addSubview, layout 지정 ... //
}
// Main view
class MainViewController: UIViewController {
// label
private lazy var processLabel: UILabel = {
let label = UILabel()
label.textColor = .darkGray
label.font = .systemFont(ofSize: 35, weight: .regular)
// label.isHidden = true
return label
}()
private lazy var resultLabel: UILabel = {
let label = UILabel()
label.textColor = .white
label.font = .systemFont(ofSize: 75, weight: .medium)
return label
}()
// 위에서 정의한 ButtonArea 호출
private lazy var buttonArea: UIView = ButtonArea()
override func viewDidLoad() {
super.viewDidLoad()
// preview를 위한 임시 조치 : SceneDelegate에서 backgroundColor를 .black으로 지정해도 #Preview에는 적용이 되지 않아 추가한다.
view.backgroundColor = .black
addSubview()
layout()
bind()
}
// 선언한 sub view들을 현재 view에 등록
private func addSubview() {
[processLabel, resultLabel, buttonArea]
.forEach {
view.addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
}
}
// 레이아웃 : ButtonArea(UIView)를 휴대폰 화면 양옆, 아래 끝에 붙이고 16.0의 간격을 준다.
// label들은 ButtonArea의 top 가장자리 기준으로 위로 8.0 띄운다.
// ButtonArea는 추후에 auto layout을 구체화한다면 화면 높이 기준으로 계산하여 적용해야 하지만, 임시로 500으로 고정한다.
private func layout() {
let superView = view.safeAreaLayoutGuide
let inset: CGFloat = 16.0
NSLayoutConstraint.activate([
buttonArea.leadingAnchor.constraint(equalTo: superView.leadingAnchor, constant: inset),
buttonArea.trailingAnchor.constraint(equalTo: superView.trailingAnchor, constant: -inset),
buttonArea.bottomAnchor.constraint(equalTo: superView.bottomAnchor, constant: -inset),
buttonArea.heightAnchor.constraint(equalToConstant: 500),
resultLabel.trailingAnchor.constraint(equalTo: buttonArea.trailingAnchor),
resultLabel.bottomAnchor.constraint(equalTo: buttonArea.topAnchor, constant: -8.0),
processLabel.trailingAnchor.constraint(equalTo: resultLabel.trailingAnchor),
processLabel.bottomAnchor.constraint(equalTo: resultLabel.topAnchor)
])
}
// label text를 view 구성을 위해 임시 지정
private func bind() {
resultLabel.text = "32,416.67"
processLabel.text = "778,000÷24"
}
}
오늘은 처음으로 화면을 구성하느라 임의로 지정한 값들이 있는데, auto layout
을 적용함에 따라 button
의 크기나 간격 등을 수정해나갈 것이다.
그리고, view
구성이 어느정도 이루어지면 button
의 동작을 추가해 계산 기능
을 수행하도록 할 것이다.
오늘은 Today I learnt
보다는 Today I coded
느낌이지만(TIC?), 위에서 말한 auto layout
적용과 button action
구현 시 많은 배움이 이루어지지 않을까 싶다.
아 드디어.. 눈물난다... 에밀리님이 벨로그를.. 흡..하업..ㅎ오애애애앵
과제도 없는데 스스로 플젝을 만드는 모습이 멋져요 ! 나이스 에밀리이이