Se ha denunciado esta presentación.
Utilizamos tu perfil de LinkedIn y tus datos de actividad para personalizar los anuncios y mostrarte publicidad más relevante. Puedes cambiar tus preferencias de publicidad en cualquier momento.
class CardTableViewCell: UITableViewCell
class ProductCardView: UIViewclass CardTableViewCell: UITableViewCell
protocol CardViewModelType {
var cardType: CardType { get }
var title: String? { get }
var subTitle: String? { get }
var r...
struct CardViewModel: CardViewModelType {
init(cardType: CardType, product: Product) {
self.cardType = cardType
self.title...
...
if let product = product, let productId = product.id {
self.wish = self.didWish?.asDriver(onErrorJustReturn: false)
.w...
protocol CardViewType {
var coverImageView: UIImageView? { get }
...
}
extension CardType: CardViewType {
var coverImageVi...
final class CardView: UIView, CardViewType {
let coverImageView: UIImageView?
...
private var disposeBag: DisposeBag
requi...
func configure(by viewModel: CardViewModelType) {
self.disposeBag = viewModel.disposeBag
self.coverImageView?.setImage(
wi...
...
private func configureLayout(by viewModel: CardViewModelType) {
switch viewModel.cardType {
case .big: self.configureL...
protocol CardViewConatinerType {
var cardView: CardViewType? { get }
func configure(with cardViewModel: CardViewModelType)...
class CardTableViewCell: UITableViewCell, CardViewContainerType {
var cardView: CardViewType? {
return self.contentView.su...
let identifier = CardCollectionViewCell.className
let cell: CardCollectionViewCell = collectionView
.dequeueReusableCell(
...
enum CardType {
case small, big
var cellSize: CGSize {
switch self {
case .small:
return CGSize(width: 160, height: 200)
c...
enum CardType {
case small, big, realFinalISwearGodFinalType
var cellSize: CGSize {
switch self {
case .small:
return CGSi...
protocol CardViewModelType {
var cardType: CardType { get }
var title: String? { get }
var subTitle: String? { get }
var r...
protocol CardViewType {
var coverImageView: UIImageView? { get }
...
}
extension CardType: CardViewType {
var coverImageVi...
final class CardView: UIView, CardViewType {
let coverImageView: UIImageView?
...
private var disposeBag: DisposeBag
requi...
func configure(by viewModel: CardViewModelType) {
self.disposeBag = viewModel.disposeBag
self.coverImageView?.setImage(
wi...
...
private func configureLayout(by viewModel: CardViewModelType) {
switch viewModel.cardType {
case .big: self.configureL...
final class CardButton: UIView, CardViewContainerType {
var cardView: CardViewType? {
return self.subviews.first as? CardV...
let viewModel = CardViewModel(cardType: .realFinalISwearGodFinalType, data: $0)
let button = CardButton(cardViewModel: vie...
기획, 디자인 변경에 강한 카드뷰 만들기 - iOS Tech Talk 2017
기획, 디자인 변경에 강한 카드뷰 만들기 - iOS Tech Talk 2017
기획, 디자인 변경에 강한 카드뷰 만들기 - iOS Tech Talk 2017
기획, 디자인 변경에 강한 카드뷰 만들기 - iOS Tech Talk 2017
기획, 디자인 변경에 강한 카드뷰 만들기 - iOS Tech Talk 2017
기획, 디자인 변경에 강한 카드뷰 만들기 - iOS Tech Talk 2017
기획, 디자인 변경에 강한 카드뷰 만들기 - iOS Tech Talk 2017
기획, 디자인 변경에 강한 카드뷰 만들기 - iOS Tech Talk 2017
기획, 디자인 변경에 강한 카드뷰 만들기 - iOS Tech Talk 2017
기획, 디자인 변경에 강한 카드뷰 만들기 - iOS Tech Talk 2017
기획, 디자인 변경에 강한 카드뷰 만들기 - iOS Tech Talk 2017
기획, 디자인 변경에 강한 카드뷰 만들기 - iOS Tech Talk 2017
기획, 디자인 변경에 강한 카드뷰 만들기 - iOS Tech Talk 2017
기획, 디자인 변경에 강한 카드뷰 만들기 - iOS Tech Talk 2017
Próxima SlideShare
Cargando en…5
×

기획, 디자인 변경에 강한 카드뷰 만들기 - iOS Tech Talk 2017

174 visualizaciones

Publicado el

기획, 디자인 변경에 강한 카드뷰 만들기 - iOS Tech Talk 2017
2017년 2월 16일 쿠팡에서 발표.

Publicado en: Móvil
  • Sé el primero en comentar

  • Sé el primero en recomendar esto

기획, 디자인 변경에 강한 카드뷰 만들기 - iOS Tech Talk 2017

  1. 1. class CardTableViewCell: UITableViewCell
  2. 2. class ProductCardView: UIViewclass CardTableViewCell: UITableViewCell
  3. 3. 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) } } }
  4. 4. 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 } }() ...
  5. 5. ... 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 } } }
  6. 6. 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 } } ... }
  7. 7. 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) } ...
  8. 8. 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) } } ...
  9. 9. ... 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) { ... } }
  10. 10. 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) } }
  11. 11. 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) } }
  12. 12. 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
  13. 13. 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) } } }
  14. 14. 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) } } }
  15. 15. 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 } }
  16. 16. 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 } } ... }
  17. 17. 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) } ...
  18. 18. 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) } } ...
  19. 19. ... 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) { ... } }
  20. 20. 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) } }
  21. 21. let viewModel = CardViewModel(cardType: .realFinalISwearGodFinalType, data: $0) let button = CardButton(cardViewModel: viewModel) self.stackView.addArrangedSubview(button)

×