Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Card view iOS Tech Talk

1fa9cb8c7997c8c4d3d251fb5e41f749?s=47 Realm
February 20, 2017

Card view iOS Tech Talk

Card View - iOS Tech Talk 2017
Wanbok Choi

1fa9cb8c7997c8c4d3d251fb5e41f749?s=128

Realm

February 20, 2017
Tweet

Transcript

  1. ӝദ ٣੗ੋ߸҃Ҋాী ъೠ஠ٜ٘࠭݅ӝ ୭৮ࠂ DIPJ!XBOCPLDPN GCDPNXBOCPL

  2. None
  3. ஠٘࠭$BSE7JFX ↟ 6*5BCMF7JFX$FMM ↟ 6*$PMMFDUJPO7JFX$FMM

  4. None
  5. class CardTableViewCell: UITableViewCell

  6. class ProductCardView: UIView class CardTableViewCell: UITableViewCell

  7. ҳઑ

  8. .PEFM 7JFX.PEFM FOVN $BSE5ZQF 7JFX \tQSPEVDUu\j^^ TNBMM CJH

  9. .PEFM 7JFX.PEFM FOVN $BSE5ZQF 7JFX \tQSPEVDUu\j^^ TNBMM CJH

  10. protocol CardViewModelType { var cardType: CardType { get } var

    title: String? { get } var subTitle: String? { get } var rating: Double? { get } var reviewCount: Int? { get } var profileImageButtonViewModel: ProfileImageButtonViewModelType? { get } var coverImageURLString: String? { get } var tags: [String]? { get } // Input var didWish: PublishSubject<Bool>? { get } // Output var wish: Driver<Bool>? { get } var disposeBag: DisposeBag { get } } enum CardType { case small, big var cellSize: CGSize { switch self { case .small: return CGSize(width: 160, height: 200) case .big: return CGSize(width: 250, height: 230) } } }
  11. struct CardViewModel: CardViewModelType { init(cardType: CardType, product: Product) { self.cardType

    = cardType self.title = product.title self.subTitle = product.catchPhrase self.coverImageURLString = product?.imageURLs?.first self.profileImageButtonViewModel = { guard let host = product.host else { return nil } return ProfileImageButtonViewModel(profileType: .host(host), size: cardType.circleImageSize) }() self.rating = product.rating self.reviewCount = product.reviewCount self.tags = { if let tags = product.areaTags? .filter({ $0.label?.characters.count ?? 0 > 0 }) .map({ $0.label ?? "" }), tags.count > 0 { return tags } else if let tags = product.locationTags? .filter({ $0.label?.characters.count ?? 0 > 0 }) .map({ $0.label ?? "" }), tags.count > 0 { return tags } else { return nil } }() ...
  12. ... if let product = product, let productId = product.id

    { self.wish = self.didWish?.asDriver(onErrorJustReturn: false) .withLatestFrom(Driver.just(productId)) { ($0, $1) } .flatMap { w, pId in Router.ProductWishToggle(["productId": pId]).request .rx.json() .asDriver(onErrorJustReturn: [:]) .map { (JSON($0)["success"].bool ?? false) ? !w : w } } .startWith(product.isWished) // Sync wishes self.wish?.withLatestFrom(Driver.just(product)) { ($0, $1) } .drive(onNext: { $0.1.isWished = $0.0 }) .addDisposableTo(self.disposeBag) } else { self.wish = nil } } }
  13. .PEFM 7JFX.PEFM FOVN $BSE5ZQF 7JFX \tQSPEVDUu\j^^ TNBMM CJH

  14. protocol CardViewType { var coverImageView: UIImageView? { get } ...

    } extension CardType: CardViewType { var coverImageView: UIImageView? { switch self { case .big: let imageView = UIImageView(frame: self.coverImageSize.rect) ... return imageView case .small: let imageView = UIImageView(frame: self.coverImageSize.rect) ... return imageView } } ... }
  15. final class CardView: UIView, CardViewType { let coverImageView: UIImageView? ...

    private var disposeBag: DisposeBag required init(on superview: UIView, with viewModel: CardViewModelType, inset: UIEdgeInsets = .zero) { self.coverImageView = viewModel.cardType.coverImageView ... super.init(frame: viewModel.cardType.cellSize.rect) superview.addSubview(self) self.snp.makeConstraints { $0.size.equalTo(viewModel.cardType.cellSize) $0.edges.equalTo(superview.snp.edges).inset(inset) } self.configure(by: viewModel) self.configureLayout(by: viewModel) } ...
  16. func configure(by viewModel: CardViewModelType) { self.disposeBag = viewModel.disposeBag self.coverImageView?.setImage( with:

    viewModel.coverImageURLString, transformation: viewModel.cardType.coverImageSize.cloudinaryTransformation ) ... self.favoriteButton?.removeTarget(self, action: nil, for: .allEvents) if let favoriteButton = self.favoriteButton { let needUserName = User.notification .map { $0.name?.isEmpty ?? true } let tapFollowButton = favoriteButton.rx.tap.asDriver() .withLatestFrom(needUserName) { $1 } .flatMap { needUserName -> Driver<Bool> in guard needUserName else { return Driver.just(false) } return User.noNameAlert.rx.alert().asDriver(onErrorJustReturn: false) } tapFollowButton .filter { $0 }.map { _ in } .drive(ProfileViewController.present) .addDisposableTo(self.disposeBag) tapFollowButton .filter { !$0 } .withLatestFrom(viewModel.wish!) { $1 } .drive(viewModel.didWish!) .addDisposableTo(self.disposeBag) viewModel.wish? .drive(favoriteButton.rx.isSelected) .addDisposableTo(self.disposeBag) } } ...
  17. ... private func configureLayout(by viewModel: CardViewModelType) { switch viewModel.cardType {

    case .big: self.configureLayoutForBig(by: viewModel) case .small: self.configureLayoutForSmall(by: viewModel) } } private func configureLayoutForBig(by viewModel: CardViewModelType) { // Construct Views self.addSubviews([self.coverImageView, ...]) ... // Layout Views self.coverImageView?.snp.makeConstraints { $0.size.equalTo(viewModel.cardType.coverImageSize) $0.top.equalToSuperview() $0.left.equalToSuperview() $0.right.equalToSuperview() } ... } private func configureLayoutForSmall(by viewModel: CardViewModelType) { ... } }
  18. $BSE7JFX DPOUFOU7JFX 6*$PMMFDUJPO7JFX$FMM

  19. protocol CardViewConatinerType { var cardView: CardViewType? { get } func

    configure(with cardViewModel: CardViewModelType) } class CardCollectionViewCell: UICollectionViewCell, CardViewContainerType { var cardView: CardViewType? { return self.contentView.subviews.first as? CardViewType } func configure(with cardViewModel: CardViewModelType) { guard let cardView = self.cardView else { let _ = CardView(on: self.contentView, with: cardViewModel) // Initialize return } cardView.configure(by: cardViewModel) } override func prepareForReuse() { super.prepareForReuse() self.cardView?.coverImageView?.image = nil self.cardView?.profileImageButton?.setImage(nil, for: .normal) } }
  20. class CardTableViewCell: UITableViewCell, CardViewContainerType { var cardView: CardViewType? { return

    self.contentView.subviews.first as? CardViewType } func configure(with cardViewModel: CardViewModelType) { guard let cardView = self.cardView else { let cardView = CardView(on: self.contentView, with: cardViewModel, inset: Metric.cellInset) self.backgroundColor = UIColor.clear cardView.borderColor = UIColor.lightblue cardView.borderWidth = 1 return } cardView.configure(by: cardViewModel) } override func prepareForReuse() { super.prepareForReuse() self.cardView?.coverImageView?.image = nil self.cardView?.profileImageButton?.setImage(nil, for: .normal) } }
  21. let identifier = CardCollectionViewCell.className let cell: CardCollectionViewCell = collectionView .dequeueReusableCell(

    withReuseIdentifier: identifier, for: indexPath ) as! CardCollectionViewCell let viewModel = CardViewModel( cardType: item.cardType, data: item.data ) cell.configure(with: viewModel) return cell
  22. None
  23. ৘द

  24. ਋ܻоցޖցޖજইೞҊनաҊ ц੘झۣҊ݄Ӓ۠ ࢜۽਍ӝദ غ૑ ƀ ࠗఌ೧_ पઁ৬࢚ҙহחо࢚੄࢚ടੑפ׮

  25. enum CardType { case small, big var cellSize: CGSize {

    switch self { case .small: return CGSize(width: 160, height: 200) case .big: return CGSize(width: 250, height: 230) } } }
  26. enum CardType { case small, big, realFinalISwearGodFinalType var cellSize: CGSize

    { switch self { case .small: return CGSize(width: 160, height: 200) case .big: return CGSize(width: 250, height: 230) case .realFinalISwearGodFinalType: return CGSize(width: 320, height: 100) } } }
  27. protocol CardViewModelType { var cardType: CardType { get } var

    title: String? { get } var subTitle: String? { get } var rating: Double? { get } var reviewCount: Int? { get } var profileImageButtonViewModel: ProfileImageButtonViewModelType? { get } var coverImageURLString: String? { get } var tags: [String]? { get } // Input var didWish: PublishSubject<Bool>? { get } // Output var wish: Driver<Bool>? { get } var disposeBag: DisposeBag { get } } ߸҃ࢎ೦হ਺
  28. protocol CardViewType { var coverImageView: UIImageView? { get } ...

    } extension CardType: CardViewType { var coverImageView: UIImageView? { switch self { case .big: let imageView = UIImageView(frame: self.coverImageSize.rect) ... return imageView case .small: let imageView = UIImageView(frame: self.coverImageSize.rect) ... return imageView case .realFinalISwearGodFinalType: return nil } } ... }
  29. OJM OJM

  30. final class CardView: UIView, CardViewType { let coverImageView: UIImageView? ...

    private var disposeBag: DisposeBag required init(on superview: UIView, with viewModel: CardViewModelType, inset: UIEdgeInsets = .zero) { self.coverImageView = viewModel.cardType.coverImageView ... super.init(frame: viewModel.cardType.cellSize.rect) superview.addSubview(self) self.snp.makeConstraints { $0.size.equalTo(viewModel.cardType.cellSize) $0.edges.equalTo(superview.snp.edges).inset(inset) } self.configure(by: viewModel) self.configureLayout(by: viewModel) } ... ߸҃ࢎ೦হ਺
  31. func configure(by viewModel: CardViewModelType) { self.disposeBag = viewModel.disposeBag self.coverImageView?.setImage( with:

    viewModel.coverImageURLString, transformation: viewModel.cardType.coverImageSize.cloudinaryTransformation ) ... self.favoriteButton?.removeTarget(self, action: nil, for: .allEvents) if let favoriteButton = self.favoriteButton { let needUserName = User.notification .map { $0.name?.isEmpty ?? true } let tapFollowButton = favoriteButton.rx.tap.asDriver() .withLatestFrom(needUserName) { $1 } .flatMap { needUserName -> Driver<Bool> in guard needUserName else { return Driver.just(false) } return User.noNameAlert.rx.alert().asDriver(onErrorJustReturn: false) } tapFollowButton .filter { $0 }.map { _ in } .drive(ProfileViewController.present) .addDisposableTo(self.disposeBag) tapFollowButton .filter { !$0 } .withLatestFrom(viewModel.wish!) { $1 } .drive(viewModel.didWish!) .addDisposableTo(self.disposeBag) viewModel.wish? .drive(favoriteButton.rx.isSelected) .addDisposableTo(self.disposeBag) } } ... ߸҃ࢎ೦হ਺
  32. ... private func configureLayout(by viewModel: CardViewModelType) { switch viewModel.cardType {

    case .big: self.configureLayoutForBig(by: viewModel) case .small: self.configureLayoutForSmall(by: viewModel) case .realFinalISwearGodFinalType: self.configureLayoutForFinal(by: viewModel) } } private func configureLayoutForFinal(by viewModel: CardViewModelType) { ... } private func configureLayoutForBig(by viewModel: CardViewModelType) { ... } private func configureLayoutForSmall(by viewModel: CardViewModelType) { ... } }
  33. final class CardButton: UIView, CardViewContainerType { var cardView: CardViewType? {

    return self.subviews.first as? CardViewType } func configure(with cardViewModel: CardViewModelType) { guard let cardView = self.cardView else { let cardView = CardView(on: self, with: cardViewModel) cardView.isUserInteractionEnabled = false return } cardView.configure(by: cardViewModel) } required init(cardViewModel: CardViewModelType) { super.init(frame: cardViewModel.cardType.cellSize.rect)) self.configure(with: cardViewModel) } }
  34. let viewModel = CardViewModel(cardType: .realFinalISwearGodFinalType, data: $0) let button =

    CardButton(cardViewModel: viewModel) self.stackView.addArrangedSubview(button)
  35. хࢎ೤פ׮