본문 바로가기

iOS 개발/Apple App Store 클론 코딩

AppleAppStore - 18. DetailViewController의 section, cell 구현 7

반응형

안녕하세요 Skillist입니다!! 드디어 막바지에 도달했습니다.

 

현재 개발 상황을 보면, 마지막 section과 cell이에요!



나~~중에 추가될수도 있겠지만, 어떻게 될지 모르겠네요.

 

시작해 보시죠

 

이번엔 아래의 section을 구현합니다! expandable cell로 구현구현돼있어요.

이슈가 하나 있는데, expand 할때마다, 스크롤 위치가 튀는 현상이 있는데, 열심히 구글링 해봐야겠어요.

 

그럼 바로 개발 해볼게요

 

------------------------------------------------------------------------------------------------------------------

 

section 구현입니다.

 

342라인 : item 구현입니다.

 

347라인 : group입니다.

 

353라인 : section입니다.

 

358라인 : header입니다.

 

------------------------------------------------------------------------------------------------------------------

 

header는 기존 header을 사용하고, button만 숨겼습니다. 따라서, cell만 구현해볼게요!

하나의 셀에는 확장할 수 없는 cell과 확장 전, 후 총 세가지의 레이아웃이 있습니다. 그래서 세가지의 제약사항을 구현해야해요.

 

이렇게 이렇게 구현하면 되는데요, 생각보다 쉽거든요.

expand 버튼을 터치하면 제약사항을 변경해주면 되죠?? 그쵸??? 귀찮을뿐 간단하네요??

 

12라인 : expand 여부를 저장하기 위해서, cell에 대한 indepPath 저장 프로퍼티를 사용합니다.

 

13라인 : expand 후 레이아웃 업데이트를 위하여 delegate를 사용합니다. 지난 expand Cell을 구현할때도 해당 델리게이트를 사용했죠?

 

14라인 : cell을 잘 보면, 0번째 cell에는 위쪽에 line이 없습니다. 1번째 cell부터 라인이 존재하는데, 이를 위해, view tag를 상수로 선언했습니다. tag를 통해서 view를 추가하고 삭제할거에요.

 

16라인 : lineView입니다. tag를 추가했습니다.

 

23라인 : titleLabel입니다. 제일 왼쪽에 있는 label입니다.

 

31라인 : title label 오른쪽에 위치하는 간략 label입니다.

 

40라인 : expand 버튼 입니다. 

 

49라인 : expand시 보여줄 상세 label 입니다.

 

57라인 : prepareForReuse는 셀을 재사용하기전에 호출됩니다. prepareForReuse에서 수정한 속성을 초기화합니다. expand시 hide한 라벨과 버튼을 show해줍니다. 다른 제약사항들은 다음에 설정할거에요.

 

65라인 : 이니셜라이저입니다. expand 버튼에 대한 action을 설정해줬어요.

 

76라인 : addTarget으로 버튼 터치시 expandCell() 호출을 구현했습니다.

 

79라인 : expand시, expand 상태를 저장하고, expand 레이아웃을 구성합니다.

 

86라인 : expand된 레이아웃을 구성합니다.

 

87, 88라인 : 간략 설명 label과 버튼을 hide합니다.

 

90라인 : 상세 라벨을 추가합니다.

 

92라인 : 먼저 아래의 140라인을 봐주세요. infoTitleLabel의 최초 제약사항이, bottom도 설정돼있습니다. 하지만, expand시, bottom 설정은 필요가 없기에, 설정돼있는 removeConstraints()를 통해, 제약사항을 제거하고 다시 설정합니다.

 

93, 100라인 : expand 레이아웃에 맞게 제약사항을 구성합니다.

 

107라인 : expand 후 delegate를 통해 레이아웃을 업데이트 합니다.

 

110라인 : item을 통해 cell을 설정합니다. 전달인자의 값으로 다양한 레이아웃이 구성될겁니다!!! 변신 로봇????

 

112라인 : row가 0인 경우 line을 추가하지 않습니다.

 

119라인 : 상세 내용이 없는 경우, expand 할수 없는 레이아웃으로 설정하고 종료합니다.

 

123라인 : 상세내용이 있다면, expand 할수 있는 레이아웃을 설정합니다.

 

126라인 : 이미 cell을 expand했다면, expand 레이아웃을 설정합니다.

어때요? 이해 되시나요??????

 

setupItem이란 이름의 다중 함수를 선언했는데요, 파라미터를 다르게 하여, 오버로딩입니다.

 

131라인 : expand 불가능한 레이아웃으로 제약사항을 구성합니다.

 

155라인 : expand 가능한 레이아웃으로 구성합니다.

 

187라인 : 상단의 line을 추가합니다.

 

------------------------------------------------------------------------------------------------------------------

 

여러분! 우리는 다양한 레이아웃을 가지는 하나의 cell을 구현하였습니다. 어떠신가요???? 

 

제 설명을 보면 어질어질하세요?

제가 봐도 어질어질하네요.

그렇다면 설명 없이 코드만 쭉 읽어보세요.

코드만 보는편이 오히려 이해가 잘 될 수 있습니다. 오히려 좋아~~

 

이만 마치도록 하겠습니다.

 

여러분 정말로 고생많으셨습니다!!!!!!

 

잘못되거나 부족한 내용 등, 피드백 감사합니다!

 

Skillist의 AppleAppStore 프로젝트

https://github.com/DeveloperSkillist/AppleAppStoreCloneCode

 

GitHub - DeveloperSkillist/AppleAppStoreCloneCode: AppleAppStoreCloneCode

AppleAppStoreCloneCode. Contribute to DeveloperSkillist/AppleAppStoreCloneCode development by creating an account on GitHub.

github.com

 

 

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓  전체 코드  ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

    private func createInfoListItemSection() -> NSCollectionLayoutSection {
        let itemMargin: CGFloat = 5
        let sectionMargin: CGFloat = 15
        
        //item
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(1))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets = .init(top: itemMargin, leading: itemMargin, bottom: itemMargin, trailing: itemMargin)
        
        //group
        let width = self.view.frame.width - sectionMargin
        let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(width), heightDimension: .estimated(1))
        let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item])
//        group.contentInsets = .init(top: 0, leading: 0, bottom: 0, trailing: 0)
        
        //section
        let section = NSCollectionLayoutSection(group: group)
        section.orthogonalScrollingBehavior = .none
        section.contentInsets = .init(top: sectionMargin, leading: sectionMargin, bottom: sectionMargin, trailing: sectionMargin)
        
        //header
        let sectionHeader = createHeader()
        section.boundarySupplementaryItems = [sectionHeader]
        
        return section
    }
class DetailInfoInfoCollectionViewCell: UICollectionViewCell {
    
    var indexPath: IndexPath?
    weak var collectionViewLayoutUpdateDelegate: CollectionViewLayoutUpdateDelegate?
    let lineViewTag = 1
    
    private lazy var lineView: UIView = {
        let view = UIView()
        view.backgroundColor = .lightGray
        view.tag = lineViewTag
        return view
    }()
    
    private lazy var infoTitleLabel: UILabel = {
        var label = UILabel()
        label.textColor = .lightGray
        label.numberOfLines = 1
        label.backgroundColor = .systemBackground
        return label
    }()
    
    private lazy var shortInfoLabel: UILabel = {
        var label = UILabel()
        label.textColor = .label
        label.textAlignment = .right
        label.numberOfLines = 1
        label.backgroundColor = .systemBackground
        return label
    }()
    
    private lazy var moreButton: UIButton = {
        var button = UIButton()
        button.setTitle("", for: .normal)
        button.setImage(UIImage.init(systemName: "chevron.down"), for: .normal)
        button.tintColor = .label
        button.backgroundColor = .systemBackground
        return button
    }()
    
    private lazy var detailInfoLabel: UILabel = {
        var label = UILabel()
        label.textColor = .label
        label.numberOfLines = 0
        label.backgroundColor = .systemBackground
        return label
    }()
    
    override func prepareForReuse() {
        super.prepareForReuse()
        shortInfoLabel.isHidden = false
        moreButton.isHidden = false
        
        viewWithTag(lineViewTag)?.removeFromSuperview()
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        setupAction()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupAction() {
        moreButton.addTarget(self, action: #selector(expandCell), for: .touchUpInside)
    }
    
    @objc private func expandCell() {
        if let indexPath = indexPath {
            collectionViewLayoutUpdateDelegate?.expandCell(indexPath: indexPath)
        }
        showDetailInfo()
    }
    
    private func showDetailInfo() {
        shortInfoLabel.isHidden = true
        moreButton.isHidden = true
        
        addSubview(detailInfoLabel)
        
        infoTitleLabel.snp.removeConstraints()
        infoTitleLabel.snp.makeConstraints {
            $0.top.equalToSuperview().inset(10)
            $0.leading.equalToSuperview()
            $0.height.equalTo(30)
            $0.width.equalTo(100)
        }
        
        detailInfoLabel.snp.makeConstraints {
            $0.top.equalTo(infoTitleLabel.snp.bottom)
            $0.leading.equalTo(infoTitleLabel)
            $0.trailing.equalToSuperview()
            $0.bottom.equalToSuperview().inset(10)
        }
        
        collectionViewLayoutUpdateDelegate?.collectionViewLayoutUpdate()
    }
    
    func setupItem(infoName: String, shortInfo: String, detailInfo: String?, isExpanded: Bool) {
        
        if let row = indexPath?.row, row > 0 {
            addLineView()
        }
        
        infoTitleLabel.text = infoName
        shortInfoLabel.text = shortInfo
        
        guard let detailInfo = detailInfo else {
            setupItem(infoName: infoName, shortInfo: shortInfo)
            return
        }
        detailInfoLabel.text = detailInfo
        
        setupItem(infoName: infoName, shortInfo: shortInfo, detailInfo: detailInfo)
        if isExpanded {
            showDetailInfo()
        }
    }
    
    private func setupItem(infoName: String, shortInfo: String) {
        [
            infoTitleLabel,
            shortInfoLabel
        ].forEach {
            addSubview($0)
        }
        
        infoTitleLabel.snp.makeConstraints {
            $0.top.bottom.equalToSuperview().inset(10)
            $0.leading.equalToSuperview()
            $0.height.equalTo(30)
            $0.width.equalTo(100)
        }
        
        shortInfoLabel.snp.makeConstraints {
            $0.top.bottom.equalTo(infoTitleLabel)
            $0.leading.equalTo(infoTitleLabel.snp.trailing).offset(10)
            $0.trailing.equalToSuperview()
        }
        
        sizeToFit()
    }
    
    private func setupItem(infoName: String, shortInfo: String, detailInfo: String) {
        [
            infoTitleLabel,
            shortInfoLabel,
            moreButton
        ].forEach {
            addSubview($0)
        }
        
        infoTitleLabel.snp.makeConstraints {
            $0.top.bottom.equalToSuperview().inset(10)
            $0.leading.equalToSuperview()
            $0.height.equalTo(30)
            $0.width.equalTo(100)
        }
        
        shortInfoLabel.snp.makeConstraints {
            $0.top.bottom.equalTo(infoTitleLabel)
            $0.leading.equalTo(infoTitleLabel.snp.trailing).offset(10)
        }
        
        moreButton.snp.makeConstraints {
            $0.top.equalTo(infoTitleLabel)
            $0.leading.equalTo(shortInfoLabel.snp.trailing).offset(10)
            $0.trailing.equalToSuperview()
            $0.bottom.equalTo(infoTitleLabel)
            $0.width.equalTo(30)
        }
        
        sizeToFit()
    }
    
    private func addLineView() {
        [
            lineView
        ].forEach {
            addSubview($0)
        }
        
        lineView.snp.makeConstraints {
            $0.top.leading.trailing.equalToSuperview()
            $0.height.equalTo(1)
        }
    }
}

 

반응형