본문 바로가기

iOS-Study

iOS Study : 10주차 - 오토레이아웃 정리(2)

코드로 오토레이아웃 작업

  • 스토리보드에서 작업할 필요 없이 코드로도 작업이 가능하다.
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let myFirstView = UIView()
        
        //코드로 오토레이아웃 설정시 필수
        myFirstView.translatesAutoresizingMaskIntoConstraints = false
        myFirstView.backgroundColor = .systemPink
        
        //밑의 코드에서 view는 스토리보드에서 보여지는 최상단의 뷰를 의미한다.
        //새롭게 추가하는 뷰는 서브뷰를 뜻하고 위애서 설정한 뷰를 넣으면 된다.
        self.view.addSubview(myFirstView)
        
        //x축, y축 위치
        //새로 추가한 myFirstView의 x축 앵커를 상위 뷰의 중간과 똑같이 맞추겠다는 의미
        myFirstView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
        //위 코드와 비슷하고 얼마만큼 떨어트려 놓을 건지를 표시
        myFirstView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 200).isActive = true
        
        //가로, 세로 크기
        myFirstView.widthAnchor.constraint(equalToConstant: 300).isActive = true
        myFirstView.heightAnchor.constraint(equalToConstant: 300).isActive = true
        
        
    }


}
  • 위 코드의 결과가 왼쪽이고 오른쪽은 스토리보드에서 만든 것이다.

 

ViewController에서 프리뷰 설정

  • storyboard로 프로젝트를 만든 경우 코드를 통해서 프리뷰를 생성할 수 있다.
  • SwiftUI로 프로젝트를 만든 경우 option + command + Enter로 프리뷰를 불러올 수 있다.
#if DEBUG
import SwiftUI

struct ViewControllerRepresentable: UIViewControllerRepresentable {
    //Update
    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
        
    }
    
    //makeUI
    @available(iOS 13.0, *)
    func makeUIViewController(context: Context) -> UIViewController {
        ViewController()
    }
}

struct ViewController_Previews: PreviewProvider {
    static var previews: some View {
        ViewControllerRepresentable()
    }
}
#endif

코드를 통해서 ViewController에서 만든 오토레이아웃을 프리뷰를 통해 보는 것이다.

 

클로저로 뷰를 만들어서 적용시키기

import UIKit

class ViewController: UIViewController {

    //클로저로 뷰 설정
    var mySecondView : UIView = {
       let view = UIView()
        //Xcode 13부터 Color Literal을 지원하지 않기 때문에 #colorLiteral( 를 쳐줘야 색상 선택이 가능하다.
        //이미지는 color대신 image
        view.backgroundColor = #colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1)
        view.layer.cornerRadius = 16
        view.translatesAutoresizingMaskIntoConstraints = false
        view.clipsToBounds = true   //cornerRadius가 잘 적용되기 위해서 필수
        return view
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let myFirstView = UIView()
        
        //코드로 오토레이아웃 설정시 필수
        myFirstView.translatesAutoresizingMaskIntoConstraints = false
        myFirstView.backgroundColor = .systemPink
        
        //밑의 코드에서 view는 스토리보드에서 보여지는 최상단의 뷰를 의미한다.
        //새롭게 추가하는 뷰는 서브뷰를 뜻하고 위애서 설정한 뷰를 넣으면 된다.
        self.view.addSubview(myFirstView)
        
        //x축, y축 위치
        //새로 추가한 myFirstView의 x축 앵커를 상위 뷰의 중간과 똑같이 맞추겠다는 의미
        myFirstView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
        //위 코드와 비슷하고 얼마만큼 떨어트려 놓을 건지를 표시
        myFirstView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 200).isActive = true
        
        //가로, 세로 크기
        myFirstView.widthAnchor.constraint(equalToConstant: 200).isActive = true
        myFirstView.heightAnchor.constraint(equalToConstant: 200).isActive = true
        myFirstView.layer.cornerRadius = 30
        
        //클로저로 만든 뷰 추가
        self.view.addSubview(mySecondView)
        NSLayoutConstraint.activate([   //배열을 만들어서 넣으두면 isActive = true를 쓸 필요 없음
            //secondeView의 크기 고정
            mySecondView.widthAnchor.constraint(equalToConstant: 100),
            mySecondView.heightAnchor.constraint(equalToConstant: 100),
            
            //mySecondView의 leadingAnchor를 myFirstView의 leadingAnchor에 맞추고 10만큼 떨어트림
            mySecondView.leadingAnchor.constraint(equalTo: myFirstView.leadingAnchor, constant: 10),
            mySecondView.topAnchor.constraint(equalTo: myFirstView.bottomAnchor, constant: 20),
        ])
    }


}

클로저로 외부에서 뷰를 만들어서 적용시킨 것이다.

 

외부 파일에서 옵션을 설정하여 적용시키기

  • MyCircleView.swift 파일을 만들어서 cornerRadius에 관한 옵션을 설정해 주고 ViewController에서 적용시킨다.
import Foundation
import UIKit

class MyCircleView: UIView {
    
    override func layoutSubviews() {
        super.layoutSubviews()
        print("MyCircleView - layoutSubView() called")
        self.layer.cornerRadius = self.frame.height / 2
    }
}
import UIKit

class ViewController: UIViewController {

    //클로저로 뷰 설정
    var mySecondView : UIView = {
       let view = UIView()
        //Xcode 13부터 Color Literal을 지원하지 않기 때문에 #colorLiteral( 를 쳐줘야 색상 선택이 가능하다.
        //이미지는 color대신 image
        view.backgroundColor = #colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1)
        view.layer.cornerRadius = 16
        view.translatesAutoresizingMaskIntoConstraints = false
        view.clipsToBounds = true   //cornerRadius가 잘 적용되기 위해서 필수
        return view
    }()
    
    //외부 파일에서 설정한 옵션을 가져와서 적용시킨 뷰
    var myThirdView : MyCircleView = {
       let circleView = MyCircleView()
        circleView.backgroundColor = #colorLiteral(red: 0.3647058904, green: 0.06666667014, blue: 0.9686274529, alpha: 1)
        circleView.translatesAutoresizingMaskIntoConstraints = false
        return circleView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let myFirstView = UIView()
        
        //코드로 오토레이아웃 설정시 필수
        myFirstView.translatesAutoresizingMaskIntoConstraints = false
        myFirstView.backgroundColor = .systemPink
        
        //밑의 코드에서 view는 스토리보드에서 보여지는 최상단의 뷰를 의미한다.
        //새롭게 추가하는 뷰는 서브뷰를 뜻하고 위애서 설정한 뷰를 넣으면 된다.
        self.view.addSubview(myFirstView)
        
        //x축, y축 위치
        //새로 추가한 myFirstView의 x축 앵커를 상위 뷰의 중간과 똑같이 맞추겠다는 의미
        myFirstView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
        //위 코드와 비슷하고 얼마만큼 떨어트려 놓을 건지를 표시
        myFirstView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 200).isActive = true
        
        //가로, 세로 크기
        myFirstView.widthAnchor.constraint(equalToConstant: 200).isActive = true
        myFirstView.heightAnchor.constraint(equalToConstant: 200).isActive = true
        myFirstView.layer.cornerRadius = 30
        
        //클로저로 만든 뷰 추가
        self.view.addSubview(mySecondView)
        NSLayoutConstraint.activate([   //배열을 만들어서 넣으두면 isActive = true를 쓸 필요 없음
            //secondeView의 크기 고정
            mySecondView.widthAnchor.constraint(equalToConstant: 100),
            mySecondView.heightAnchor.constraint(equalToConstant: 100),
            
            //mySecondView의 leadingAnchor를 myFirstView의 leadingAnchor에 맞추고 10만큼 떨어트림
            mySecondView.leadingAnchor.constraint(equalTo: myFirstView.leadingAnchor, constant: 10),
            mySecondView.topAnchor.constraint(equalTo: myFirstView.bottomAnchor, constant: 20),
        ])
        
        self.view.addSubview(myThirdView)
        myThirdView.widthAnchor.constraint(equalTo: mySecondView.widthAnchor, multiplier: 1.5).isActive = true
        myThirdView.heightAnchor.constraint(equalTo: mySecondView.heightAnchor, multiplier: 1.5).isActive = true
        myThirdView.topAnchor.constraint(equalTo: mySecondView.bottomAnchor, constant: 50).isActive = true
        myThirdView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
        
    }


}

 

 

오픈소스 활용 - SnapKit

  • SnapKit은 오토레이아웃을 잡을 때 도움을 주는 라이브러리이다.
  • https://github.com/SnapKit/SnapKit 이 경로로 들어가서 Swift Pakage Manager에 있는 경로를 복사한다.
  • Xcode의 File에서 Add Pakages를 눌러 복사해둔 경로인 https://github.com/SnapKit/SnapKit.git 를 붙여서 패키지를
    다운받으면 SnapKit 패키지를 다운받게 된다.

SnapKit을 이용하여 간단한 기능 구현

  • 버튼 클릭시 녹색 박스 이동
import UIKit
import SnapKit  //SnapKit을 임포트

class ViewController: UIViewController {
    
    //클로저로 생성
    lazy var greenBox = { () -> UIView in
        let view = UIView()
        view.backgroundColor = .green
        return view
    }()
    
    lazy var redBox = { () -> UIView in
        let view = UIView()
        view.backgroundColor = .red
        return view
    }()
        
    lazy var yellowBox = { () -> UIView in
        let view = UIView()
        view.backgroundColor = .yellow
        return view
    }()
        
    lazy var blueBox = { () -> UIView in
        let view = UIView()
        view.backgroundColor = .blue
        return view
    }()
    
    //버튼 생성, UIColor를 매개변수로 받음
    lazy var myButton = { (color: UIColor) -> UIButton in
        //type을 system으로 하면 클릭 효과
        let btn = UIButton(type: .system)
        btn.backgroundColor = color
        btn.setTitle("내 버튼", for: .normal)
        btn.titleLabel?.font = UIFont.boldSystemFont(ofSize: 40)
        btn.setTitleColor(.white, for: .normal)
        btn.layer.cornerRadius = 16
        return btn
    }
        
//    var greenBoxTopNSLayoutConstraint : NSLayoutConstraint? = nil
    
    var greenBoxTopConstraint : Constraint? = nil

    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.view.addSubview(yellowBox)
        self.view.addSubview(greenBox)
        self.view.addSubview(redBox)
        self.view.addSubview(blueBox)
        
        //위에서 설정한 매개변수에 색을 넣어줌
        let myDarkGrayBtn = myButton(.darkGray)
        self.view.addSubview(myDarkGrayBtn)
        
//        yellowBox.translatesAutoresizingMaskIntoConstraints = false
//        greenBox.translatesAutoresizingMaskIntoConstraints = false
//        redBox.translatesAutoresizingMaskIntoConstraints = false
//        blueBox.translatesAutoresizingMaskIntoConstraints = false
//
//        //기존 오토레이아웃
//        yellowBox.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 20).isActive = true
//        yellowBox.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -20).isActive = true
//        yellowBox.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 20).isActive = true
//        yellowBox.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -20).isActive = true
        
        //스냅킷 사용하면 더 간단하게 표현이 가능하다. 위 코드를 아래 코드 하나로 구성한 것이다.
        //yellowBox의 Top, Bottom, Leading, Trailing을 맞추고, padding값도 준다.
        //스냅킷을 사용하려면 snp를 써주어야 한다.
        yellowBox.snp.makeConstraints { make in
            make.edges.equalTo(self.view).inset(UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20))
            //equalToSuperview()를 사용하면 최상위 뷰의 맞추다는 의미이다.
//            make.edges.equalToSuperview().inset(UIEdgeInsets(top: 100, left: 50, bottom: 50, right: 50))
        }
        
        redBox.snp.makeConstraints { (make) in
            //크기만 고정시키는 것도 가능
            make.width.height.equalTo(100)
            make.top.equalTo(self.view.safeAreaLayoutGuide.snp.top)
            make.centerX.equalToSuperview()
//            make.center.equalToSuperview()    //한 가운데로 고정시키는 코드
        }
                
        //스냅킷
        blueBox.snp.makeConstraints { (make) in
            //redBox의 width 크긱의 2배
            make.width.equalTo(redBox.snp.width).multipliedBy(2)
            make.height.equalTo(redBox.snp.height)
            
            //offset을 통해 띄울 수 있다.
            make.top.equalTo(redBox.snp.bottom).offset(20)
            make.centerX.equalToSuperview()
        }
        
        //스냅킷
        myDarkGrayBtn.snp.makeConstraints { (make) in
            make.width.equalTo(200)
            make.height.equalTo(100)
            make.bottom.equalTo(self.view.safeAreaLayoutGuide.snp.bottom)
            make.centerX.equalToSuperview()
        }
        
        //버튼 클리시 이동하는 기능
        myDarkGrayBtn.addTarget(self, action: #selector(moveGreenBoxDown), for: .touchUpInside)
                
        //스냅킷
        greenBox.snp.makeConstraints { (make) in
            make.width.height.equalTo(100)
            make.centerX.equalToSuperview()
            self.greenBoxTopConstraint = make.top.equalTo(blueBox.snp.bottom).offset(20).constraint
        }
    }
    
    var offset = 0
        
    //버튼 클릭시 초록색 박스 이동
    @objc fileprivate func moveGreenBoxDown(){
        offset += 40
        print("ViewController - moveGreenBoxDown() called / offset: \(offset)")
        
        //스냅킷
        self.greenBoxTopConstraint?.update(offset: offset)
        
        
//        self.greenBoxTopNSLayoutConstraint?.priority = 900
//        self.greenBoxTopNSLayoutConstraint?.constant = CGFloat(offset)
        
        //greenBox 이동시 애니메이션
        UIViewPropertyAnimator(duration: 0.2, curve: .easeOut, animations: {
            self.view.layoutIfNeeded()
        }).startAnimation()
        
        
    }


}

#if DEBUG
import SwiftUI
struct ViewControllerRepresentable: UIViewControllerRepresentable {
    
func updateUIViewController(_ uiView: UIViewController,context: Context) {
        // leave this empty
}
@available(iOS 13.0.0, *)
func makeUIViewController(context: Context) -> UIViewController{
    ViewController()
    }
}
@available(iOS 13.0, *)
struct ViewControllerRepresentable_PreviewProvider: PreviewProvider {
    static var previews: some View {
        Group {
            ViewControllerRepresentable()
                .ignoresSafeArea()
                .previewDisplayName(/*@START_MENU_TOKEN@*/"Preview"/*@END_MENU_TOKEN@*/)
                .previewDevice(PreviewDevice(rawValue: "iPhone 11"))
        }
        
    }
}
#endif

 

코드로 오토레이아웃 작성하기 - 애니메이션 처리

  • 버튼 클릭시 뷰가  이동
import UIKit

class ViewController: UIViewController {
    
    var someViewBottomConstraint : NSLayoutConstraint?
    
    //viewDidLoad()보다 먼저 로드가 됨
    override func loadView() {
        super.loadView()
        
        //Assets에 등록한 색상을 불러옴
        view.backgroundColor = UIColor(named: "MY_YELLOW")
        
        let someView = UIView()
        someView.backgroundColor = UIColor(named: "MY_BLUE")
        someView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(someView)
        someView.layer.cornerRadius = 8
        NSLayoutConstraint.activate([
            someView.widthAnchor.constraint(equalToConstant: 100),
            someView.heightAnchor.constraint(equalToConstant: 50),
            someView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
            //safeArea의 bottom에 앵커를 건다.
//            someView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor)
        ])
        
        //위에서 사용한 코드 대신 밑에 코드를 이용해 어떤 뷰의 어디를
        //어디에 맞춰서 앵커를 얼마큼 걸 것인지를 설정한 것이다.
        someViewBottomConstraint = NSLayoutConstraint(item: someView, attribute: .bottom, relatedBy: .equal, toItem: self.view.safeAreaLayoutGuide, attribute: .bottom, multiplier: 1.0, constant: 0)
        someViewBottomConstraint?.isActive = true
        
        let moveViewUpBtn = UIButton(type: .system)
        moveViewUpBtn.translatesAutoresizingMaskIntoConstraints = false
        moveViewUpBtn.backgroundColor = .white
        moveViewUpBtn.setTitle("위로 올리기", for: .normal)
        moveViewUpBtn.titleLabel?.font = UIFont.boldSystemFont(ofSize: 30)
        moveViewUpBtn.setTitleColor(.black, for: .normal)
        
        //버튼 글자와 버튼 사이의 간격
        //밑에서 사용한 contentEdgeIsets는 iOS 15이후로 지원하지 않기 때문에 그 밑에 코드를 사용해야 한다.
//        moveViewUpBtn.contentEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
        moveViewUpBtn.configuration?.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 100, bottom: 10, trailing: 10)
        moveViewUpBtn.layer.cornerRadius = 8
        moveViewUpBtn.addTarget(self, action: #selector(moveViewUp), for: .touchUpInside)
        self.view.addSubview(moveViewUpBtn)
        moveViewUpBtn.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
        moveViewUpBtn.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 150).isActive = true
        
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @objc fileprivate func moveViewUp(){
        print("ViewController - moveViewUp() called")
        
        someViewBottomConstraint?.constant -= 100
        
        //애니메이션 설정
        UIViewPropertyAnimator(duration: 0.3, curve: .easeOut, animations: { [weak self] in
            guard let self = self else { return }
            self.view.layoutIfNeeded()
        }).startAnimation()
        
    }


}

#if DEBUG
import SwiftUI
struct ViewControllerRepresentable: UIViewControllerRepresentable {
    
    // update
    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
        
    }
    // makeui
    @available(iOS 13.0, *)
    func makeUIViewController(context: Context) -> UIViewController {
        ViewController()
    }
}
        
struct ViewController_Previews: PreviewProvider {
    static var previews: some View {
        ViewControllerRepresentable()
        .edgesIgnoringSafeArea(.all)
        .previewDisplayName("미리보기")
    }
}


#endif

 

콜렉션뷰 - 콤포지셔널 레이아웃

사전 작업

  • 이전 주차에 했던 다이나믹 뷰를 가져와서 추가 작업을 진행한다. (영상 참고)
  • 새로 만든 ViewController에 버튼 두개를 만들어서 설정한 후 테이블뷰 버튼을 기존에 있던 TableView에 드래그하여 show를 클릭하고, 새로 ViewController를 하나 더 만들어 콜렉션뷰를 드래그 한다.
  • ViewController를 MyTableVC로 바꾸고, MyCollectionVC, MainVC 파일을 만들어서 각 ViewController에 연결해준다.
  • Main 부분에서 아래처럼 NavigationController를 클릭해서 넘어가는 방식을 바꿔준다.
  • 이렇게 세팅하고 작업을 하면 된다.

 

콜렉션뷰 작업

  1. 스토리보드에서 디자인하고 코드로 넣는 방법
    • Main에서 콜렉션뷰 부분에 필요한 오브젝트들을 넣어주고 설정해 준다.
    • MyCollectionViewCell.swift 파일을 만들어서 코드를 작성한다.
    • MyCollectionVC.swift 파일에 코드를 작성한다.
  2. Nib 파일을 만들어서 불러오는 방법 
    • MyCustomCollectionViewCell.xib 파일을 만들어서 디자인 한다.
    • MyCustomCollectionViewCell.swift파일을 만들어서 코드를 작성한다.
    • MyCollectionVC.swift 파일에 코드를 작성하여 Nib 파일을 불러와서 적용시킨다.

MyCollectionViewCell.swift 코드

import Foundation
import UIKit

class MyCollectionViewCell: UICollectionViewCell {
    
    
    @IBOutlet weak var profileImg: UIImageView!
    @IBOutlet weak var profileLabel: UILabel!
    
    //이미지와 라벨에 대한 설정을 여기에서 해주고 MyCollectionVC에서 호출도 가능하다.
//    var imageName : String = "" {
//        didSet{
//            print("MyCollectionViewCell / imageName - didSet() : \(imageName)")
//            // 쏄의 UI 설정
//            self.profileImg.image = UIImage(systemName: imageName)
//            self.profileLabel.text = imageName
//        }
//    }
    
    //MyCollectionVC에서 설정 안하고 여기에서 설정도 가능하다.
//    override func awakeFromNib() {
//        super.awakeFromNib()
//        print("MyCollectionViewCell - awakeFromNib() called")
//        self.contentView.backgroundColor = #colorLiteral(red: 0.9764705896, green: 0.850980401, blue: 0.5490196347, alpha: 1)
//        self.contentView.layer.cornerRadius = 8
//        contentView.layer.borderWidth = 1
//        contentView.layer.borderColor = #colorLiteral(red: 0.3647058904, green: 0.06666667014, blue: 0.9686274529, alpha: 1)
//    }
}

Nib 파일을 불러오지 않은 경우의 MyCollectionVC.swift 코드

import Foundation
import UIKit

class MyCollectionVC: UIViewController {
    
    @IBOutlet weak var mySegmentControl: UISegmentedControl!
    @IBOutlet weak var myCollectionView: UICollectionView!
    //시스템 이미지 배열
    fileprivate let systemImageNameArray = [
            "moon", "zzz", "sparkles", "cloud", "tornado", "smoke.fill", "tv.fill", "gamecontroller", "headphones", "flame", "bolt.fill", "hare", "tortoise", "moon", "zzz", "sparkles", "cloud", "tornado", "smoke.fill", "tv.fill", "gamecontroller", "headphones", "flame", "bolt.fill", "hare", "tortoise", "ant", "hare", "car", "airplane", "heart", "bandage", "waveform.path.ecg", "staroflife", "bed.double.fill", "signature", "bag", "cart", "creditcard", "clock", "alarm", "stopwatch.fill", "timer"
        ]
    
    //MARK: - Lifecycles
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //콜렉션뷰에 대한 설정
        myCollectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        myCollectionView.dataSource = self
        myCollectionView.delegate = self
    }
}


//데이터 소스 설정 - 데이터와 관련된 것들
extension MyCollectionVC: UICollectionViewDataSource {
    
    //각 섹션에 들어가는 아이템 갯수
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return self.systemImageNameArray.count
    }
    
    //각 콜렉션뷰 셀에 대한 설정
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
        let cellId = String(describing: MyCollectionViewCell.self)
        
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MyCollectionViewCell
        
    
        
        cell.contentView.backgroundColor = #colorLiteral(red: 0.9764705896, green: 0.850980401, blue: 0.5490196347, alpha: 1)
        cell.contentView.layer.cornerRadius = 8
        cell.contentView.layer.borderWidth = 1
        cell.contentView.layer.borderColor = #colorLiteral(red: 0.721568644, green: 0.8862745166, blue: 0.5921568871, alpha: 1)
        
        //MyCollectionViewCell에 이미지와 라벨에 대한 설정을 한 후 불러오는 방법
//        cell.imageName = self.systemImageNameArray[indexPath.item]
        
        //데이터에 따른 셀 UI 변경
        //이미지에 대한 설정
        cell.profileImg.image = UIImage(systemName: self.systemImageNameArray[indexPath.item])
        //라벨 설정
        cell.profileLabel.text = self.systemImageNameArray[indexPath.item]
        
        return cell
    }
    
    
}


//콜렉션뷰 델리겟 - 액션과 관련된 것들
extension MyCollectionVC: UICollectionViewDelegate {
    
}

MyCustomCollectionViewCell.swift 코드

import Foundation
import UIKit

class MyCustomCollectionViewCell: UICollectionViewCell {
    
    
    @IBOutlet weak var profileImg: UIImageView!
    @IBOutlet weak var profileLabel: UILabel!
    
    
    //이미지와 라벨에 대한 설정을 여기에서 해주고 MyCollectionVC에서 호출도 가능하다.
    var imageName : String = "" {
        didSet{
            // 쏄의 UI 설정
            self.profileImg.image = UIImage(systemName: imageName)
            self.profileLabel.text = imageName
        }
    }
    
    //MyCollectionVC에서 설정 안하고 여기에서 설정도 가능하다.
    override func awakeFromNib() {
        super.awakeFromNib()
        self.contentView.backgroundColor = #colorLiteral(red: 0.9584091306, green: 0.6374807358, blue: 0.746566236, alpha: 1)
        self.contentView.layer.cornerRadius = 8
        contentView.layer.borderWidth = 1
        contentView.layer.borderColor = #colorLiteral(red: 0.721568644, green: 0.8862745166, blue: 0.5921568871, alpha: 1)
    }
}

Nib 파일을 가져와서 사용하는 경우의 MyCollectionVC.swift 코드

import Foundation
import UIKit

class MyCollectionVC: UIViewController {
    
    @IBOutlet weak var mySegmentControl: UISegmentedControl!
    @IBOutlet weak var myCollectionView: UICollectionView!
    //시스템 이미지 배열
    fileprivate let systemImageNameArray = [
            "moon", "zzz", "sparkles", "cloud", "tornado", "smoke.fill", "tv.fill", "gamecontroller", "headphones", "flame", "bolt.fill", "hare", "tortoise", "moon", "zzz", "sparkles", "cloud", "tornado", "smoke.fill", "tv.fill", "gamecontroller", "headphones", "flame", "bolt.fill", "hare", "tortoise", "ant", "hare", "car", "airplane", "heart", "bandage", "waveform.path.ecg", "staroflife", "bed.double.fill", "signature", "bag", "cart", "creditcard", "clock", "alarm", "stopwatch.fill", "timer"
        ]
    
    //MARK: - Lifecycles
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //콜렉션뷰에 대한 설정
        myCollectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        myCollectionView.dataSource = self
        myCollectionView.delegate = self
        
        //사용한 컬렉션뷰 셀을 등록
        // 닙파일을 가져온다
        let myCustomCollectionViewCellNib = UINib(nibName: String(describing: MyCustomCollectionViewCell.self), bundle: nil)
        
        // 가져온 닙파일로 콜렉션뷰에 쎌로 등록한다
        self.myCollectionView.register(myCustomCollectionViewCellNib, forCellWithReuseIdentifier: String(describing: MyCustomCollectionViewCell.self))
    }
}


//데이터 소스 설정 - 데이터와 관련된 것들
extension MyCollectionVC: UICollectionViewDataSource {
    
    //각 섹션에 들어가는 아이템 갯수
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return self.systemImageNameArray.count
    }
    
    //각 콜렉션뷰 셀에 대한 설정
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
//        let cellId = String(describing: MyCollectionViewCell.self)
        
//        //셀의 인스턴스
//        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MyCollectionViewCell
        
        //Nib을 불러와서 사용할 때
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: MyCustomCollectionViewCell.self), for: indexPath) as! MyCustomCollectionViewCell
        
//        cell.contentView.backgroundColor = #colorLiteral(red: 0.9764705896, green: 0.850980401, blue: 0.5490196347, alpha: 1)
//        cell.contentView.layer.cornerRadius = 8
//        cell.contentView.layer.borderWidth = 1
//        cell.contentView.layer.borderColor = #colorLiteral(red: 0.721568644, green: 0.8862745166, blue: 0.5921568871, alpha: 1)
        
        //MyCollectionViewCell에 이미지와 라벨에 대한 설정을 한 후 불러오는 방법
        cell.imageName = self.systemImageNameArray[indexPath.item]
        
        //데이터에 따른 셀 UI 변경
        //이미지에 대한 설정
//        cell.profileImg.image = UIImage(systemName: self.systemImageNameArray[indexPath.item])
//        //라벨 설정
//        cell.profileLabel.text = self.systemImageNameArray[indexPath.item]
        
        return cell
    }
    
    
}


//콜렉션뷰 델리겟 - 액션과 관련된 것들
extension MyCollectionVC: UICollectionViewDelegate {
    
}

 

콤포지셔널 레이아웃을 사용

  • 콜렉션뷰 이지만 테이블처럼 보여지게 할 수 있다.

콤포지셔널 레이아웃 사용한 상태의 MyCollectionVC.swift 코드

import Foundation
import UIKit

class MyCollectionVC: UIViewController {
    
    @IBOutlet weak var mySegmentControl: UISegmentedControl!
    @IBOutlet weak var myCollectionView: UICollectionView!
    //시스템 이미지 배열
    fileprivate let systemImageNameArray = [
            "moon", "zzz", "sparkles", "cloud", "tornado", "smoke.fill", "tv.fill", "gamecontroller", "headphones", "flame", "bolt.fill", "hare", "tortoise", "moon", "zzz", "sparkles", "cloud", "tornado", "smoke.fill", "tv.fill", "gamecontroller", "headphones", "flame", "bolt.fill", "hare", "tortoise", "ant", "hare", "car", "airplane", "heart", "bandage", "waveform.path.ecg", "staroflife", "bed.double.fill", "signature", "bag", "cart", "creditcard", "clock", "alarm", "stopwatch.fill", "timer"
        ]
    
    //MARK: - Lifecycles
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //콜렉션뷰에 대한 설정
        myCollectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        myCollectionView.dataSource = self
        myCollectionView.delegate = self
        
        //사용한 컬렉션뷰 셀을 등록
        // 닙파일을 가져온다
        let myCustomCollectionViewCellNib = UINib(nibName: String(describing: MyCustomCollectionViewCell.self), bundle: nil)
        
        // 가져온 닙파일로 콜렉션뷰에 쎌로 등록한다
        self.myCollectionView.register(myCustomCollectionViewCellNib, forCellWithReuseIdentifier: String(describing: MyCustomCollectionViewCell.self))
        
        //콜렉션뷰의 콜렉션뷰 레이아웃을 설정한다.
        self.myCollectionView.collectionViewLayout = createCompositionalLayoutForFirst()
    }
    
    
    @IBAction func onCollectionViewTypeChanged(_ sender: UISegmentedControl) {
        switch sender.selectedSegmentIndex {
        case 0:
            self.myCollectionView.collectionViewLayout = createCompositionalLayoutForFirst()
        case 1:
            self.myCollectionView.collectionViewLayout = createCompositionalLayoutForSecond()
        case 2:
            self.myCollectionView.collectionViewLayout = createCompositionalLayoutForThird()
        default:
            break
        }
        
    }
    
}

//MARK: - 콜렉션뷰 콤포지셔널 레이아웃 관련
extension MyCollectionVC {
    
    //콤포지셔널 레이아웃 설정
    fileprivate func createCompositionalLayoutForFirst() -> UICollectionViewLayout {
        //콤포지셔널 레이아웃 생성
        let layout = UICollectionViewCompositionalLayout{
            //만들게 되면 튜플(키: 값, 키:값)의 묶음으로 들어온다.
            //반환하는 것은 NSCollectionLayoutSection 콜렉션 레이아웃 섹션을 반환해야함
            (sectionIndex: Int, LayoutEnvironment: NSCollectionLayoutEnvironment) ->
                NSCollectionLayoutSection? in
            
            //아이템에 대한 사이즈 - absolute는 고정값, estimated는 추측, fraction은 퍼센트
            //.fractionalWidth(1.0) - 전체 가로높이의 100%
            let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(100))
            
            //위에서 만든 아이템 사이즈로 아이템 만들기
            let item = NSCollectionLayoutItem(layoutSize: itemSize)
            
            //아이템간의 간격 설정
            item.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
            
            //그룹 사이즈
            let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: itemSize.heightDimension)
            
            let groupHeight = NSCollectionLayoutDimension.fractionalHeight(1/3)
            
            //그룹 사이즈로 그룹 만들기
//            let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item, item, item])
            let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1)
            
            //그룹으로 섹션 만들기
            let section = NSCollectionLayoutSection(group: group)
//            section.orthogonalScrollingBehavior = . continuous  //오른쪽으로 스크롤 가능
            
            //섹션에 대한 간격 설정
            section.contentInsets = NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20)
            
            return section
        }
        return layout
        
    }
    
    //콤포지셔널 레이아웃 설정
    fileprivate func createCompositionalLayoutForSecond() -> UICollectionViewLayout {
        //콤포지셔널 레이아웃 생성
        let layout = UICollectionViewCompositionalLayout{
            //만들게 되면 튜플(키: 값, 키:값)의 묶음으로 들어온다.
            //반환하는 것은 NSCollectionLayoutSection 콜렉션 레이아웃 섹션을 반환해야함
            (sectionIndex: Int, LayoutEnvironment: NSCollectionLayoutEnvironment) ->
                NSCollectionLayoutSection? in
            
            //아이템에 대한 사이즈 - absolute는 고정값, estimated는 추측, fraction은 퍼센트
            //.fractionalWidth(1.0) - 전체 가로높이의 100%
            let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(100))
            
            //위에서 만든 아이템 사이즈로 아이템 만들기
            let item = NSCollectionLayoutItem(layoutSize: itemSize)
            
            //아이템간의 간격 설정
            item.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
            
            //그룹 사이즈
            let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: itemSize.heightDimension)
            
            let groupHeight = NSCollectionLayoutDimension.fractionalHeight(1/2)
            
            //그룹 사이즈로 그룹 만들기
//            let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item, item, item])
            let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 2)
            
            //그룹으로 섹션 만들기
            let section = NSCollectionLayoutSection(group: group)
//            section.orthogonalScrollingBehavior = . continuous  //오른쪽으로 스크롤 가능
            
            //섹션에 대한 간격 설정
            section.contentInsets = NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20)
            
            return section
        }
        return layout
        
    }
    
    //콤포지셔널 레이아웃 설정
    fileprivate func createCompositionalLayoutForThird() -> UICollectionViewLayout {
        //콤포지셔널 레이아웃 생성
        let layout = UICollectionViewCompositionalLayout{
            //만들게 되면 튜플(키: 값, 키:값)의 묶음으로 들어온다.
            //반환하는 것은 NSCollectionLayoutSection 콜렉션 레이아웃 섹션을 반환해야함
            (sectionIndex: Int, LayoutEnvironment: NSCollectionLayoutEnvironment) ->
                NSCollectionLayoutSection? in
            
            //아이템에 대한 사이즈 - absolute는 고정값, estimated는 추측, fraction은 퍼센트
            //.fractionalWidth(1.0) - 전체 가로높이의 100%
            let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(100))
            
            //위에서 만든 아이템 사이즈로 아이템 만들기
            let item = NSCollectionLayoutItem(layoutSize: itemSize)
            
            //아이템간의 간격 설정
            item.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
            
            //그룹 사이즈
            let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: itemSize.heightDimension)
            
            let groupHeight = NSCollectionLayoutDimension.fractionalHeight(1/3)
            
            //그룹 사이즈로 그룹 만들기
//            let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item, item, item])
            let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 3)
            
            //그룹으로 섹션 만들기
            let section = NSCollectionLayoutSection(group: group)
//            section.orthogonalScrollingBehavior = . continuous  //오른쪽으로 스크롤 가능
            
            //섹션에 대한 간격 설정
            section.contentInsets = NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20)
            
            return section
        }
        return layout
        
    }
}

//데이터 소스 설정 - 데이터와 관련된 것들
extension MyCollectionVC: UICollectionViewDataSource {
    
    //각 섹션에 들어가는 아이템 갯수
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return self.systemImageNameArray.count
    }
    
    //각 콜렉션뷰 셀에 대한 설정
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
//        let cellId = String(describing: MyCollectionViewCell.self)
        
//        //셀의 인스턴스
//        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MyCollectionViewCell
        
        //Nib을 불러와서 사용할 때
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: MyCustomCollectionViewCell.self), for: indexPath) as! MyCustomCollectionViewCell
        
//        cell.contentView.backgroundColor = #colorLiteral(red: 0.9764705896, green: 0.850980401, blue: 0.5490196347, alpha: 1)
//        cell.contentView.layer.cornerRadius = 8
//        cell.contentView.layer.borderWidth = 1
//        cell.contentView.layer.borderColor = #colorLiteral(red: 0.721568644, green: 0.8862745166, blue: 0.5921568871, alpha: 1)
        
        //MyCollectionViewCell에 이미지와 라벨에 대한 설정을 한 후 불러오는 방법
        cell.imageName = self.systemImageNameArray[indexPath.item]
        
        //데이터에 따른 셀 UI 변경
        //이미지에 대한 설정
//        cell.profileImg.image = UIImage(systemName: self.systemImageNameArray[indexPath.item])
//        //라벨 설정
//        cell.profileLabel.text = self.systemImageNameArray[indexPath.item]
        
        return cell
    }
    
    
}


//콜렉션뷰 델리겟 - 액션과 관련된 것들
extension MyCollectionVC: UICollectionViewDelegate {
    
}

UITableView - diffableDataSource

  • 리스트를 보여주는 방식
  • data source를 생성하고, cell proivder를 사용해서 어떤 cell을 사용할 것인지를 선택한다.
  • 스냅샷을 사용하여 스냅샷에 따른 UI를 보여주게 된다.

ViewController.swift 코드

import UIKit

//이넘 섹션
enum Section {
    case feed, post, board
}

//클래스로 diffableDataSource 사용
class Feed: Hashable {
    //diffableDataSource는 값이 유일해야 하기 때문에 구분해 줘야 한다.
    let uuid = UUID()
    var conetnt: String
    init(conetnt: String) {
        self.conetnt = conetnt
    }
    
    static func == (lhs: Feed, rhs: Feed) -> Bool {
        lhs.uuid == rhs.uuid
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(uuid)
    }
}

//스트럭트로 diffableDataSource 사용
struct Post: Hashable {
    var content: String
}


class ViewController: UIViewController {
    
    //중요한 구성 3가지
    //1. 테이블 뷰
    @IBOutlet weak var myTableView: UITableView!
    
    //2. 데이터 소스 - UITableViewDataSource를 대체
    var dataSource: UITableViewDiffableDataSource<Section, Feed>!
    
    //3. 스냅샷 - 현재의 데이터 상태
    var snapShot: NSDiffableDataSourceSnapshot<Section, Feed>!
    
    let feedArray = [
        Feed(conetnt: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla sollicitudin cursus mi, a cursus leo consectetur vel. Sed maximus sapien est, ac hendrerit dolor suscipit vel. Mauris consectetur turpis faucibus metus efficitur pharetra. Donec ullamcorper neque sit amet lacinia suscipit. Cras facilisis pulvinar dignissim. Morbi in vulputate dolor, vitae laoreet nisi. Fusce tincidunt urna vel efficitur auctor. Proin suscipit sodales tincidunt. Nam sed mattis turpis, quis gravida quam. Proin in consequat magna. Pellentesque mollis velit nec purus dapibus viverra. Curabitur ac tempus ex, ac volutpat felis. In ornare velit sit amet mi fermentum aliquam. Donec fermentum libero eros, et gravida dui sollicitudin at."),
        
        Feed(conetnt: "Mauris non tincidunt nulla. Sed dictum semper elit eget molestie. Morbi ac purus non velit efficitur rutrum vel nec diam. Phasellus auctor erat vel tempor accumsan. Sed dapibus magna sed urna aliquam auctor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed volutpat erat posuere porta ultricies."),
        
        Feed(conetnt: "Quisque non nibh sed mi suscipit hendrerit at eget tellus. Cras lectus erat, vehicula semper sagittis id, dictum commodo est. Morbi eu urna sollicitudin, volutpat mi nec, feugiat nunc. Aliquam blandit dapibus nulla, at tristique arcu interdum vel. Cras aliquam elit quam, sit amet auctor metus faucibus laoreet. Nam eget mauris sit amet ante euismod condimentum. Donec purus neque, ullamcorper eu fermentum a, blandit venenatis nisl."),
        
        Feed(conetnt: "Sed nec sem auctor est euismod fringilla. Ut viverra orci et dictum lacinia. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer malesuada dapibus lacinia. Nulla rutrum consectetur purus, id pretium justo elementum sed. Sed bibendum erat libero, non dapibus purus interdum sed. Sed cursus nisi nec commodo semper. Duis eleifend semper diam. Curabitur ut pulvinar diam.")
    
    ]
    

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        //셀 리소스 파일 가져오기
        let myTableViewCellNib = UINib(nibName: String(describing: MyTableViewCell.self), bundle: nil)
        
        //셀에 리소스 등록
        self.myTableView.register(myTableViewCellNib, forCellReuseIdentifier: "myTableViewCell")
        
        self.myTableView.rowHeight = UITableView.automaticDimension
        self.myTableView.estimatedRowHeight = 120
        
        //아주 중요
        self.myTableView.delegate = self
//        self.myTableView.dataSource = self
        
        print("contentArray.count : \(feedArray.count)")
        
        //MARK - 데이터 소스 설정
        dataSource = UITableViewDiffableDataSource<Section, Feed>(tableView: self.myTableView, cellProvider: { (tableView: UITableView, indexPath: IndexPath, itemIdentifier: Feed) -> UITableViewCell in
            
            //셀 클래스 연결
            let cell = tableView.dequeueReusableCell(withIdentifier: "myTableViewCell", for: indexPath) as! MyTableViewCell
            
            return cell
        })
        
        //데이터 소스의 현재 스냅샷을 만든다.
        snapShot = NSDiffableDataSourceSnapshot<Section, Feed>()
        
        //섹션 추가
        snapShot.appendSections([.feed])
        
        //방금 추가한 섹션에 아이템 널기
        snapShot.appendItems(feedArray, toSection: .feed)
        
        //현재 스냅샷을 화면에 보여준다.
        dataSource.apply(snapShot, animatingDifferences: true)
        
    }


}

extension ViewController: UITableViewDelegate {
    
}