iOS 앱 개발 부트캠프/TIL

iOS 앱개발 본캠프 3일차 TIL - 팀프로젝트 테이블 뷰 구현하기

iosstudyletsgo 2024. 10. 23. 23:41

오늘은 팀 프로젝트에서 내가 맡은 부분인 테이블 뷰 구현을 시작하였다.

이렇게 생긴 화면을 구현하는 것이 목표이다. 물론 내가 할 것은 테이블 뷰이므로 탭 메뉴 아래 부분만 구현 하면 되고 나머지 부분은 다른 팀원이 구현한 화면에 내가 구현한 테이블뷰가 들어가는 형태이다.

처음엔 간단하게 테이블 뷰 안에 테이블 셀을 넣고, 테이블 셀 안에 이미지뷰와 텍스트필드를 넣은 뒤 화면이 출력 되는지부터 보고자 했다.

cmd + shift + L을 써서 해당 오브젝트드를 스토리보드에 드래그앤 드롭하여 추가하였고 IBOutlet으로 연결하였다. 그리고 테이블뷰에 필요한 delegate와 datasource 설정도 해준 뒤, 임시로 출력해볼 값들을 간단하게 변수로 선언을 하였다.

goal 변수는 텍스트 필드에 띄울 값이고 goalImg는 텍스트 필드 옆에 띄울 이미지로 생각하고 선언하였다.

그 후 셀의 수를 설정할 numberOfRowsImSection func와 셀의 내용을 설정할 cellForRowAt을 간단하게 작성하고 실행해보았다.

사전캠프중에 할 때 테이블뷰를 출력해본 적 있어서 출력 자체는 가능할 줄 알았는데 에러가 났다.

이미지랑 텍스트필드가 연결이 안되었다고 자꾸 뜨는 것이었다. 뭐가 잘못됐나 싶어서 뷰 컨트롤러와 오브젝트들의 class 설정도 확인해보고, identify 설정도 확인해보고, xcode도 재부팅해보고, outlet 연결도 다시해보고, 아예 전부 지웠다 다시해보고 했는데도 계속 같은 결과여서 이게 무슨 일인가 싶었다.

한참을 고민하다가 에러 내용들 다시 찬찬히 읽어보니, 테이블뷰는 문제가 있다는 말이 없는데 텍스트 필드와 이미지뷰만 에러가 난 것이 테이블뷰 셀의 문제인가 하는 생각이 들었다.

커스텀 셀 클래스 선언

구글과 챗지피티의 도움을 받으며 찾아보니 기본 제공하는 테이블뷰 셀에는 이미지랑 텍스트필드를 넣고 출력하는게 안되고, 커스텀 셀을 만들어야 내가 원하는 기능을 구현할 수 있는 것 같았다.

그래서 GoalVierCell 이라는 커스텀 셀 클래스를 따로 선언한 뒤에 그 안에 아웃렛 연결을 하였다. 그리고 생각해보니 정해진 문구만 출력하면 돼서 입력을 할 수 있는 텍스트 필드보단 그냥 라벨로 하는게 더 맞는 것 같아서 텍스트 필드도 라벨로 변경하였다.

이렇게 하니 에러가 사라져서 드디어 출력은 해보겠구나 싶었다.

 

생각했던 대로 셀이 출력은 됐는데 라벨 내용이 제대로 안 나오고 잘리게 나왔는데 일단 셀 모양부터 잡아보기로 했다.

테이블뷰 코드 아래에 셀의 크기를 설정할 heightForRowAt func를 새롭게 선언하고, 커스텀 셀 클래스 안에 셀 사이의 간격을 정하는 코드를 추가하여 아래 화면과 같이 출력하였다.

 

와이어 프레임에서 구현한 화면과 같이 구현하려면 좌측에 이미지 하나와 우측에 상하로 구성된 라벨 두 개가 출력되어야 해서 이미지와 라벨 두개를 출력하려고 했다. 이미지는 일단 추가하지 않았으니 빈칸으로 나오는게 맞지만, 라벨이 자꾸 이상하게 출력 되었다.

이후 오토레이아웃을 설정해서 제대로 나오도록 하려고 시도하였다.

class GoalViewCell: UITableViewCell {
    
    @IBOutlet weak var GoalImageView: UIImageView!
    @IBOutlet weak var GoalLabel: UILabel!
    @IBOutlet weak var GoalNameLabel: UILabel!
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)
            setupLabels()
            setupConstraints()
        }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupLabels()
        setupConstraints()
    }
    
    private func setupLabels() {
        //라벨 설정
        GoalNameLabel.translatesAutoresizingMaskIntoConstraints = false
        GoalNameLabel.text = "이름"
        GoalNameLabel.textAlignment = .center
        
        GoalLabel.translatesAutoresizingMaskIntoConstraints = false
        GoalLabel.text = "목표"
        GoalLabel.textAlignment = .center
        
        //셀에 라벨 추가
        contentView.addSubview(GoalNameLabel)
        contentView.addSubview(GoalLabel)
    }
    
    private func setupConstraints() {
        NSLayoutConstraint.activate([
            //이름 라벨의 상단을 셀의 상단에 맞춤
            GoalNameLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
            //이름 라벨의 중앙을 셀의 중앙에 맞춤
            GoalNameLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            
            //목표 라벨의 상단을 이름 라벨의 하단에 맞춤
            GoalLabel.topAnchor.constraint(equalTo: GoalLabel.bottomAnchor, constant: 10),
            //목표 라벨의 중앙을 셀의 중앙에 맞춤
            GoalLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor)
            ])
    }

접근 제한자와 코드 재사용성을 고려하며 작성해보라는 튜터님의 제안에 맞는 예제를 몇 개 찾아 내 코드에 맞게 조금씩 변형하며 다음과 같이 작성하였는데, 계속 에러가 났다. 해결하지 못하고 한참을 해매다가 스토리보드에서 outlet으로 오브젝트들을 연결한 뒤 오토레이아웃을 코드로 설정하려다보니 에러가 난 듯 했다.

let GoalNameLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.textAlignment = .center
        return label
    }()
    
    let GoalLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.textAlignment = .center
        return label
    }()
    let GoalImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        return imageView
    }()

이런 식으로 라벨과 이미지 설정을 따로 선언해서 조절하였다. 그리고 밑에 오토 레이아웃도 코드로 추가하였다.

//오토레이아웃
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupLabels()
        setupConstraints()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupLabels()
        setupConstraints()
    }
    
    private func setupLabels() {
        // 라벨 설정
        GoalNameLabel.translatesAutoresizingMaskIntoConstraints = false
        GoalNameLabel.text = "이름 라벨"
        GoalNameLabel.textAlignment = .center
        
        GoalLabel.translatesAutoresizingMaskIntoConstraints = false
        GoalLabel.text = "목표 라벨"
        GoalLabel.textAlignment = .center
        
        // 셀에 라벨 추가
        contentView.addSubview(GoalImageView)
        contentView.addSubview(GoalNameLabel)
        contentView.addSubview(GoalLabel)
    }
    
    private func setupConstraints() {
        NSLayoutConstraint.activate([
            // 이미지 뷰 제약 조건
            GoalImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20),
            GoalImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10),
            GoalImageView.widthAnchor.constraint(equalToConstant: 30),
            GoalImageView.heightAnchor.constraint(equalToConstant: 30),
            
            // 이름 라벨 제약 조건
            GoalNameLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20),
            GoalNameLabel.leadingAnchor.constraint(equalTo: GoalImageView.trailingAnchor, constant: 5),
            GoalNameLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10),
            
            // 목표 라벨 제약 조건
            GoalLabel.topAnchor.constraint(equalTo: GoalNameLabel.bottomAnchor, constant: 10),
            GoalLabel.leadingAnchor.constraint(equalTo: GoalImageView.trailingAnchor, constant: 5),
            GoalLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10)
        ])
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        // 모서리 둥글기 설정
        contentView.layer.cornerRadius = 10
        contentView.layer.masksToBounds = true
        
        // 셀 테두리 설정
        contentView.layer.borderWidth = 1
        contentView.layer.borderColor = UIColor.lightGray.cgColor
        
        // 여백 설정
        contentView.frame = contentView.frame.inset(by: UIEdgeInsets(top: 0, left: 0, bottom: 30, right: 0))
    }
    
}

보면 계속 반복돼서 쓰이는데, topAnchor와 leadingAnchor, widthAnchor와 heightAnchor가 계속해서 같은 방식으로 쓰였다.

topAnchor를 통해 해당 뷰나 다른 뷰의 상단, 하단 또는 안전 영역과의 거리를 조절하는 명령어이고, leadingAnchor가 뷰의 왼쪽 경계선을 다른 뷰의 왼쪽 경계선에 맞추거나 간격을 조절하는데 쓰이는 명령어, widthAnchor와 heightAnchor는 각각 뷰의 너비와 높이를 조절할 때 쓰인다.

따라서 두 개의 라벨과 이미지뷰를 해당 방식으로 오토레이아웃 하여 앱에 그려지는 뷰를 조절한 것이다. 그리고 마지막에 layoutSubviews()를 통해 모서리의 둥글기와 셀 테두리, 셀 사이의 간격을 조절하는 부분도 추가하였다.

그렇게 해서 지금의 이 화면까지 나오게 되었다. 아직 asset에 이미지를 추가하지 않아서 빈칸으로 나오고 있는데, 내일 이미지의 이름을 goalImg 배열에 저장된 이름과 잘 맞춰 asset에 추가하여 테이블뷰의

 cell.GoalImageView.image = UIImage(named: goalImg[indexPath.row]) // 이미지

이 부분으로 이미지를 불러오는 작업을 할 것이다.