티스토리 뷰

SwiftUI가 나왔지만 아직 많은 개발자들이 UIKit을 사용해서 개발하고 있기 때문에 UIKit도 공부할 필요가 있다.

프로필을 보여주는 메인 ViewController를 만들어 보자.

 

간단하게 이름과 프로필 이미지로 구성하고, 아래에 내용을 조금 추가해서 스크롤 할 수 있게 만든다.

 

XCode12부터 SwiftUI를 사용해서 개발할 수 있게 되었기 때문에 프로젝트를 만들 때 두가지를 선택할 수 있다. 여기에서는 Storyboard를 활용해서 만들 것이기 때문에 storyboard를 선택하고 넘어간다. 언어는 swift를 선택한다.

 

XCode12부터 SwiftUI를 선택할 수 있게 선택지가 생겼다.

 

프로젝트를 만들면 AppDelegate, SceneDelegate, main storyboard가 자동으로 생성되어 있는 것을 볼 수 있다.

 

  • AppDelegate : 애플리케이션의 생명 주기에 따라 발생하는 이벤트를 처리한다.
  • SceneDelegate : IOS13부터 추가된 delegate. 이전 버전에서 개발한 애플리케이션은 하나의 scene만 화면에 보였다. (App == Scene) 따라서 이 둘을 구분해서 처리할 필요가 없었지만 iPadOS13부터 애플리케이션을 동시에 2개 이상 화면에 띄울 수 있게 되었다.(App ≠ Scene) 따라서 이 각각의 scene의 생명 주기에 따라 발생하는 이벤트를 처리할 delegate가 필요해졌다.
  • main.storyboard : GUI를 조작하며 view를 디자인할 수 있다. XCode 의 기능을 사용해서 실제 코드와 연결시킬 수 있다.

 

처음 생성된 ViewController는 UIViewController를 상속 받아서 정의 되어 있다.

ViewController는 하나의 view를 가지며 이 view의 종류에 따라 UITableViewController, UICollectionViewController 등으로 사전 정의 되어있다. 또한 이 새로운 ViewController들은 자신이 가지고 있는 view에서 발생하는 이벤트를 처리하기 위해 delegate 또는 datasource 등을 확장하여 함수를 정의한다.

물론 이렇게 정의된 ViewController들도 결국 UIViewController를 상속받아 만들어 둔 클래스 이기 때문에 UIViewController를 상속 받아서 임의로 다른 ViewDelegate를 상속 받아도 같은 기능을 할 수 있다.

 

여러 view controller들이 미리 정의되어 있다.

 

프로필 화면은 ScrollView를 가져야 하기 때문에 UIScrollViewController를 찾아보았지만 scroll view controller는 따로 정의되어 있지 않다. 어쩔 수 없이 그냥 UIViewController를 상속받은 상태에서 UIScollView 를 추가한다. storyboard에서 GUI를 사용해도 되지만 이번에는 코드에서 직접 추가하자.

viewDidLoad는 view controller가 화면에 추가될 때 호출되는 함수로 이곳에서 필요한 것들을 초기화 할 수 있다.

 

override func viewDidLoad() {
	super.viewDidLoad()

	let scrollView = UIScrollView(frame: view.frame)
	scrollView.contentSize = CGSize(width: scrollView.frame.width, height: 1000)
	view.addSubView(scrollView)
}

 

이 ViewController는 UIViewController를 상속 받고 UIViewController는 최상위 view로 UIView를 가진다. 그리고 이 view의 instance 를 view 라는 변수로 가지고 있기 때문에 따로 정의하거나 @IBOutlet으로 연결하지 않아도 사용할 수 있다. 같은 이유로 UICollectionViewController는 collectionView를, UITableViewController는 tableView를 가지고 있다.

배경과 프로필 이미지를 추가할 건데, 이 모든 view들을 viewDidLoad()에서 정의하고 추가하면 코드가 너무 길어지기 때문에 각 개별 view를 생성하는 부분은 따로 빼서 작성할 수도 있다. lazy 변수에 ()→UIView closure를 호출하는 로직을 초기화 하면 맴버변수처럼 뺄 수 있다.

이 변수는 lazy로 선언되어 있기 때문에 실제로 이 변수에 접근할 때 closure가 실행된다. 즉, view controller의 instance가 생성될 때는 실행되지 않고 있다가, 후에 viewDidLoad()에서 view에 추가하기 위해 직접 접근할 때 실행된다.

 

// background
lazy var backgroundView: UIView = {
	let backgroundView = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 320))
	backgroundView.backgroundColor = UIColor.systemPurple
	
	return backgroundView
}()

// profile
lazy var profileImage: UIImageView = {
	let profileImage = UIImageView(frame: CGRecct(x: 0, y: 0, width: 200, height: 200))
	profileImage.image = UIImage(named: "profile")
	
	return profileImage
}()

// name
lazy var nameLabel: UILabel = {
	let nameLabel = UILabel(frame: CGRect(x: 0, y:0, width: 200, height: 50))
	nameLabel.text = "사나"    // asset name
	nameLabel.textAlignment = .center
	nameLabel.font = UIFont.boldSystemFont(ofSize: 17)
	
	return nameLabel
}()

 

이렇게 만든 view들은 scrollView를 추가할 때 처럼 scrollView에 추가할 수 있으며, viewDidLoad() 밖에서도 사용할 수 있다는 장점이 있다.

 

scrollView.addSubView(backgroundView)
scrollView.addSubView(profileImage)
scrollView.addSubView(nameLabel)

 

아직 각 view들의 위치를 정해 주지 않았기 때문에 실행하면 모두 왼쪽 위에 겹쳐서 그려진다. view를 생성할 때 frame의 x,y를 0,0으로 설정했기 때문인데, 이 좌표를 필요에 맞게 조절하면서 화면을 구성할 수도 있지만 이렇게 만들면 화면이 회전하거나 해상도, 화면 비율이 달라질 때 위치가 조금씩 달라진다. 이 문제를 방지하기 위해서는 AutoLayout으로 화면을 구성해야 한다.

 

화면 비율에 따라 위치가 달라질 수 있다.

 

NSLayoutConstraint라는 규칙을 적용시켜야 하는데, 이 규칙은 view hierarchy에 있는 다른 view들과 상대적인 위치 규칙을 정하는 것이기 때문에 view가 많아지면 적용하기가 꽤 까다롭다. 처음부터 구성을 잘 생각하고 만들어야 한다.

 

이 부분은 꽤 논쟁이 많은 부분인 것 같다. storyboard로 설정하는게 편한 사람도 물론 많겠지만 개인적으로는 안 그래도 느린 storyboard에 constraint를 잔뜩 추가하고 하나하나 설정하다 보면 정말 머리가 아파진다. 오히려 코드로 만들 때가 더 직관적이고 수정하기 쉬워서 앞으로 나올 모든 view들은 코드로만 만들 것이다.

 

먼저 가운데로 정렬된 화면으로 constraint를 설정한다. 처음에 초기화 했던 frame은 constraint 규칙이 활성화 하면서 하나하나 무시되기 때문에 이제 크게 의미가 없다. 단 기본적으로 frame만 설정하면 Autoresizing Mask로 위치를 정하는데, 이 때 XCode가 자동으로 이 규칙에 맞는 constraint를 추가한다.

 

지정된 규칙에 따라 상위 view의 상대적인 위치를 정의하는 constraint를 자동 설정한다.

 

그냥 임의의 constraint를 추가하면 XCode가 자동으로 추가하는 constraint와 규칙이 충돌해서 오류 메시지를 뱉어내는데, 이걸 무시하기 위해 .translatesAutoresizingMaskIntoConstraints를 false로 설정해 주어야 한다. XCode가 자동으로 constraint를 추가하지 못하도록 하는 것이다.

 

backgroundView.translatesAutoresizingMaskIntoConstraints = false

scrollView.addSubView(backgroundView)
backgroundView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
backgroundView.heightAnchor.constraint(equalToConstant: 350).isActive = true
backgroundView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true
backgroundView.widthAnchor.constraint(equalToConstant: scrollView.frame.width).isActive = true

 

또한 constraint는 상대적으로 동작하기 때문에 addSubView로 view를 다른 view에 추가한 다음에 활성화 시켜야 한다. top, leading 같은 상대적인 위치는 그에 맞는 상대적인 view의 anchor를 지정해 주어야 하지만, width, height 처럼 독립적인 규칙같은 경우 상수만 지정할 수도 있다. 같은 방법으로 profileImage와 nameLabel의 constraint도 초기화 하자.

 

// profile
scrollView.addSubView(profileImage)
profileImage.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true
profileImage.centerYAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
profileImage.widthAnchor.constraint(equalToConstant: 200).isActive = true
profileImage.heightAnchor.constraint(equalToConstant: 200).isActive = true

// name
scrollView.addSubView(nameLabel)
nameLabel.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true
nameLabel.centerYAnchor.constraint(equalTo: profileImage.bottomAnchor, constant: 50).isActive = true

 

이제 모든 view가 제자리를 찾아가고, 스크롤 하더라도 위치와 크기를 유지하고 있는 것을 볼 수 있다.

 

프로필 이미지가 동그랗게 보이는 건 다음 포스팅에서 다룬다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함