Slide 1

Slide 1 text

ࠓߋ͚ͩͲUIKitͰ ܕύϥϝʔλͷΠϯδΣΫτΛར༻ͯ͠ ViewͷϨΠΞ΢τΛͯ͠ΈΑ͏ iOSDC Japan 2022 by Taiki Suzuki / @marty_suzuki

Slide 2

Slide 2 text

ࣗݾ঺հ 🏢ɹגࣜձࣾαΠόʔΤʔδΣϯτ ɹɹmarty-suzuki 👨💻ɹϞόΠϧΞϓϦΤϯδχΞ ɹɹmarty_suzuki 🏌ɹSTEALTH / VENTUS TR 6-S

Slide 3

Slide 3 text

ABEMAͷϦετͷσβΠϯͷύλʔϯ

Slide 4

Slide 4 text

σβΠϯͷύλʔϯͷྫ

Slide 5

Slide 5 text

σβΠϯͷύλʔϯͷྫ

Slide 6

Slide 6 text

σβΠϯͷύλʔϯͷྫ

Slide 7

Slide 7 text

ຊLTͰண໨͢Δ8ύλʔϯ

Slide 8

Slide 8 text

ڞ௨ͷViewͰදݱͨ͠৔߹ͷྫ final class SummaryView: UIView { private let stackView: UIStackView private let titleLabel: UILabel private var subTextLabel: UILabel? private var additionalView: UIView? }

Slide 9

Slide 9 text

ڞ௨ͷViewͰදݱͨ͠৔߹ͷྫ final class SummaryView: UIView {

Slide 10

Slide 10 text

ڞ௨ͷViewͰදݱͨ͠৔߹ͷྫ final class SummaryView: UIView {

Slide 11

Slide 11 text

ڞ௨ͷViewͰදݱͨ͠৔߹ͷྫ final class SummaryView: UIView {

Slide 12

Slide 12 text

enumͰදݱͨ͠৔߹ͷྫ public enum SummaryCombination: Hashable { // MARK: - Basic case subTextTopBasic(startAt: String, title: String) case subTextSecondBasic(title: String, broadcastTime: String) case noSubTextBasic(title: String) // MARK: - Coin case subTextSecondCoin(title: String, duration: String, tag: Tag?, coin: Int?) case noSubTextCoin(title: String, coin: Int?, expirationTime: String?) // MARK: - ViewingType case subTextTopViewingType(seasonName: String, title: String, tag: Tag?, expirationTime: String?) case subTextSecondViewingType(title: String, broadcastTime: String, tag: Tag?, expirationTime: String?) case noSubTextViewingType(title: String, tag: Tag?, expirationTime: String?) }

Slide 13

Slide 13 text

enumͰදݱͨ͠৔߹ͷྫ public enum SummaryCombination: Hashable { case subTextTopBasic(startAt: String, title: String) case subTextSecondBasic(title: String, broadcastTime: String) case noSubTextBasic(title: String)

Slide 14

Slide 14 text

enumͰදݱͨ͠৔߹ͷྫ public enum SummaryCombination: Hashable { case subTextSecondCoin(title: String, duration: String, tag: Tag?, coin: Int?) case noSubTextCoin(title: String, coin: Int?, expirationTime: String?)

Slide 15

Slide 15 text

enumͰදݱͨ͠৔߹ͷྫ public enum SummaryCombination: Hashable { case subTextTopViewingType(seasonName: String, title: String, tag: Tag?, expirationTime: String?) case subTextSecondViewingType(title: String, broadcastTime: String, tag: Tag?, expirationTime: String?) case noSubTextViewingType(title: String, tag: Tag?, expirationTime: String?)

Slide 16

Slide 16 text

enumͰදݱͨ͠৔߹ͷྫ final class SummaryView: UIView { ... func configure(_ combination: SummaryCombination) { switch combination { case let .subTextTopBasic(startAt, title): titleLabel.text = title titleLabel.numberOfLines = 2 configureSubTextLabel(text: startAt, position: 0) additionalView?.removeFromSuperview() case let .subTextSecondBasic(title, broadcastTime): ... case let .noSubTextBasic(title): ... case let .subTextSecondCoin(title, duration, tag, coin): ... case let .noSubTextCoin(title, coin, expirationTime): ... case let .subTextTopViewingType(seasonName, title, tag, expirationTime): ... case let .subTextSecondViewingType(title, broadcastTime, tag, expirationTime): ... case let .noSubTextViewingType(title, tag, expirationTime): ... } } }

Slide 17

Slide 17 text

enumͰදݱͨ͠৔߹ͷྫ final class SummaryView: UIView {

Slide 18

Slide 18 text

enumͰදݱͨ͠৔߹ͷྫ final class SummaryView: UIView { titleLabel.text = title titleLabel.numberOfLines = 2 configureSubTextLabel(text: startAt, position: 0) additionalView?.removeFromSuperview()

Slide 19

Slide 19 text

iOSDC Japan 2022 ܕύϥϝʔλΛར༻͢Δͱ Ͳ͏͍͏࣮૷ʹͳΔ͔

Slide 20

Slide 20 text

ܕύϥϝʔλͰදݱͨ͠৔߹ͷྫ protocol SummaryRow { static func subTextLabelAndInsertionIndex() -> (UILabel, Int)? } enum SubTextTop: SummaryRow { static func subTextLabelAndInsertionIndex() -> (UILabel, Int)? { (UILabel(frame: .zero), 0) } } enum SubTextSecond: SummaryRow { static func subTextLabelAndInsertionIndex() -> (UILabel, Int)? { (UILabel(frame: .zero), 1) } } enum NoSubText: SummaryRow { static func subTextLabelAndInsertionIndex() -> (UILabel, Int)? { nil } }

Slide 21

Slide 21 text

ܕύϥϝʔλͰදݱͨ͠৔߹ͷྫ protocol SummaryRow { static func subTextLabelAndInsertionIndex() -> (UILabel, Int)? } ߦͰڞ௨ͷTVC5FYU-BCFMͷ༗ແͱදࣔҐஔ

Slide 22

Slide 22 text

ܕύϥϝʔλͰදݱͨ͠৔߹ͷྫ protocol SummaryRow { static func subTextLabelAndInsertionIndex() -> (UILabel, Int)? { (UILabel(frame: .zero), 0) } }

Slide 23

Slide 23 text

ܕύϥϝʔλͰදݱͨ͠৔߹ͷྫ protocol SummaryRow { static func subTextLabelAndInsertionIndex() -> (UILabel, Int)? { (UILabel(frame: .zero), 1) } }

Slide 24

Slide 24 text

ܕύϥϝʔλͰදݱͨ͠৔߹ͷྫ protocol SummaryRow { static func subTextLabelAndInsertionIndex() -> (UILabel, Int)? { nil } }

Slide 25

Slide 25 text

ܕύϥϝʔλͰදݱͨ͠৔߹ͷྫ protocol SummaryColumn { associatedtype AdditionalView: UIView static func additionalView() -> AdditionalView? } enum Basic: SummaryColumn { static func additionalView() -> UIView? { nil } } enum Coin: SummaryColumn { static func additionalView() -> UIView? { nil } } enum ViewingType: SummaryColumn { static func additionalView() -> TagTextView? { TagTextView(frame: .zero) } }

Slide 26

Slide 26 text

ܕύϥϝʔλͰදݱͨ͠৔߹ͷྫ protocol SummaryColumn { associatedtype AdditionalView: UIView static func additionalView() -> AdditionalView? } ྻͰڞ௨ͳΧελϜ7JFXΛBTTPDJBUFE5ZQFͰఆٛ

Slide 27

Slide 27 text

ܕύϥϝʔλͰදݱͨ͠৔߹ͷྫ protocol SummaryColumn { static func additionalView() -> UIView? { nil } }

Slide 28

Slide 28 text

ܕύϥϝʔλͰදݱͨ͠৔߹ͷྫ protocol SummaryColumn { static func additionalView() -> UIView? { nil } }

Slide 29

Slide 29 text

ܕύϥϝʔλͰදݱͨ͠৔߹ͷྫ protocol SummaryColumn { static func additionalView() -> TagTextView? { TagTextView(frame: .zero) } }

Slide 30

Slide 30 text

ܕύϥϝʔλͰදݱͨ͠৔߹ͷྫ protocol SummaryCombination { associatedtype Row: SummaryRow associatedtype Column: SummaryColumn associatedtype AdditionalView: UIView static func numberOfTitleLines() -> Int static func additionalView() -> AdditionalView? static func subTextColor() -> UIColor? } ߦͱྻͷ૊Έ߹ΘͤΛදݱ͢ΔQSPUPDPM

Slide 31

Slide 31 text

ܕύϥϝʔλͰදݱͨ͠৔߹ͷྫ enum SubTextTopBasic: SummaryCombination { typealias Row = SubTextTop typealias Column = Basic static func numberOfTitleLines() -> Int { 2 } static func additionalView() -> UIView? { nil } } enum SubTextSecondCoin: SummaryCombination { typealias Row = SubTextSecond typealias Column = Coin static func numberOfTitleLines() -> Int { 2 } static func additionalView() -> TagCoinView? { TagCoinView(frame: .zero) } } enum SubTextTopViewingType: SummaryCombination { typealias Row = SubTextTop typealias Column = ViewingType static func numberOfTitleLines() -> Int { 2 } static func additionalView() -> Column.AdditionalView? { Column.additionalView() } }

Slide 32

Slide 32 text

ܕύϥϝʔλͰදݱͨ͠৔߹ͷྫ enum SubTextTopBasic: SummaryCombination { typealias Row = SubTextTop typealias Column = Basic static func numberOfTitleLines() -> Int { 2 } static func additionalView() -> UIView? { nil } }

Slide 33

Slide 33 text

ܕύϥϝʔλͰදݱͨ͠৔߹ͷྫ enum SubTextTopBasic: SummaryCombination { typealias Row = SubTextSecond typealias Column = Coin static func numberOfTitleLines() -> Int { 2 } static func additionalView() -> TagCoinView? { TagCoinView(frame: .zero) } }

Slide 34

Slide 34 text

ܕύϥϝʔλͰදݱͨ͠৔߹ͷྫ enum SubTextTopBasic: SummaryCombination { typealias Row = SubTextTop typealias Column = ViewingType static func numberOfTitleLines() -> Int { 2 } static func additionalView() -> Column.AdditionalView? { Column.additionalView() } }

Slide 35

Slide 35 text

ܕύϥϝʔλΛར༻ͨ͠ڞ௨ͷViewͷྫ final class SummaryView: UIView { private let stackView: UIStackView private let titleLabel: UILabel private let subTextLabel: UILabel? private let additionalView: Combination.AdditionalView? override init(frame: CGRect) { let subTextLabelAndInsertionIndex = Combination.Row.subTextLabelAndInsertionIndex() self.subTextLabel = subTextLabelAndInsertionIndex?.0 self.additionalView = Combination.additionalView() super.init(frame: frame) titleLabel.numberOfLines = Combination.numberOfTitleLines() backgroundColor = .black ... stackView.addArrangedSubview(titleLabel) if let (subTextLabel, index) = subTextLabelAndInsertionIndex { subTextLabel.textColor = Combination.subTextColor() subTextLabel.font = .systemFont(ofSize: 12) stackView.insertArrangedSubview(subTextLabel, at: index) } if let additionalView = additionalView { stackView.addArrangedSubview(additionalView) } } }

Slide 36

Slide 36 text

ܕύϥϝʔλΛར༻ͨ͠ڞ௨ͷViewͷྫ final class SummaryView: UIView { private let additionalView: Combination.AdditionalView? override init(frame: CGRect) let subTextLabelAndInsertionIndex = Combination.Row.subTextLabelAndInsertionIndex() self.additionalView = Combination.additionalView() titleLabel.numberOfLines = Combination.numberOfTitleLines() subTextLabel.textColor = Combination.subTextColor() 6*7JFXͷJOJU GSBNF ΛΦʔόʔϥΠυ͍ͯ͠Δ ͷͰɺJOJUJBMJ[FS͔Β͸ґଘΛ஫ೖ͢Δ͜ͱ͸Ͱ͖ ͳ͍͕ɺܕύϥϝʔλ͔Β஫ೖ͢Δ͜ͱ͕Ͱ͖Δ

Slide 37

Slide 37 text

ܕύϥϝʔλΛར༻ͨ͠ڞ௨ͷViewͷྫ final class SummaryView: UIView { private let titleLabel: UILabel titleLabel.numberOfLines = Combination.numberOfTitleLines() stackView.addArrangedSubview(titleLabel)

Slide 38

Slide 38 text

ܕύϥϝʔλΛར༻ͨ͠ڞ௨ͷViewͷྫ final class SummaryView: UIView { private let subTextLabel: UILabel? let subTextLabelAndInsertionIndex = Combination.Row.subTextLabelAndInsertionIndex() self.subTextLabel = subTextLabelAndInsertionIndex?.0 if let (subTextLabel, index) = subTextLabelAndInsertionIndex { subTextLabel.textColor = Combination.subTextColor() subTextLabel.font = .systemFont(ofSize: 12) stackView.insertArrangedSubview(subTextLabel, at: index) }

Slide 39

Slide 39 text

ܕύϥϝʔλΛར༻ͨ͠ڞ௨ͷViewͷྫ final class SummaryView: UIView { private let additionalView: Combination.AdditionalView? self.additionalView = Combination.additionalView() if let additionalView = additionalView { stackView.addArrangedSubview(additionalView) } BTTPDJBUFE5ZQFͰܕΛఆ͍ٛͯ͠ΔͷͰɺར༻ ઌͰΩϟετͤͣʹΧελϜ7JFXͷϝιου౳ʹ ΞΫηεͰ͖Δ

Slide 40

Slide 40 text

ܕύϥϝʔλΛར༻ͨ͠ڞ௨ͷViewͷྫ extension SummaryView where Combination == SubTextTopBasic { func configure(startAt: String, title: String) { setTitle(title) setSubText(startAt) } } extension SummaryView where Combination == SubTextSecondCoin { func configure(title: String, duration: String, tag: Tag?, coin: Int?) { setTitle(title) setSubText(duration) additionalView?.configure(tag: tag, coin: coin) } } extension SummaryView where Combination == SubTextTopViewingType { func configure(seasonName: String, title: String, tag: Tag?, expirationTime: String?) { setTitle(title) setSubText(seasonName) additionalView?.configure(tag: tag, text: expirationTime) } }

Slide 41

Slide 41 text

ܕύϥϝʔλΛར༻ͨ͠ڞ௨ͷViewͷྫ extension SummaryView where Combination == SubTextTopBasic { func configure(startAt: String, title: String) { setTitle(title) setSubText(startAt) } } extension SummaryView where Combination == SubTextSecondCoin { func configure(title: String, duration: String, tag: Tag?, coin: Int?) { setTitle(title) setSubText(duration) additionalView?.configure(tag: tag, coin: coin) } } extension SummaryView where Combination == SubTextTopViewingType { func configure(seasonName: String, title: String, tag: Tag?, expirationTime: String?) { setTitle(title) setSubText(seasonName) additionalView?.configure(tag: tag, text: expirationTime) } } δΣωϦοΫXIFSF۟Ͱ$PNCJOBUJPOΛࢦఆͯ͠ $PNCJOBUJPO͝ͱͷϝιουΛఆٛ͠ɺ಺෦Ͱ͸ ܕ͕֬ఆ͍ͯ͠ΔBEEUJPOBM7JFXͷ࣮૷Λݺͼग़ ͯ͠ϨΠΞ΢τΛߋ৽͢Δ

Slide 42

Slide 42 text

enumͱܕύϥϝʔλͷύλʔϯͰUICollectionViewͷdequeueͷҧ͍ // enumͰදݱͨ͠৔߹ public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let combination = combinations[indexPath.row] let result = collectionView.reusable.dequeueReusableView(ListView.self, for: indexPath) result.view.summaryView.configure(combination) return result.cell } // ܕύϥϝʔλͰදݱͨ͠৔߹ public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { switch viewDataModels[indexPath.row] { case let .subTextTopBasic(startAt, title): let result = collectionView.reusable.dequeueReusableView(ListView>.self, for: indexPath) result.view.summaryView.configure(startAt: startAt, title: title) return result.cell case let .subTextSecondBasic(title, broadcastTime): let result = collectionView.reusable.dequeueReusableView(ListView>.self, for: indexPath) result.view.summaryView.configure(title: title, broadcastTime: broadcastTime) return result.cell ... } }

Slide 43

Slide 43 text

enumͱܕύϥϝʔλͷύλʔϯͰUICollectionViewͷdequeueͷҧ͍ // enumͰදݱͨ͠৔߹ let result = collectionView.reusable.dequeueReusableView(ListView.self, for: indexPath) result.view.summaryView.configure(combination) return result.cell ಉҰͷ7JFXΛ࢖͍ճͯ͠ɺFOVNͷDBTFʹΑͬͯ ಺෦ͰϨΠΞ΢τΛมߋ͍ͯ͠Δ

Slide 44

Slide 44 text

enumͱܕύϥϝʔλͷύλʔϯͰUICollectionViewͷdequeueͷҧ͍ // enumͰදݱͨ͠৔߹ switch viewDataModels[indexPath.row] { case let .subTextTopBasic(startAt, title): let result = collectionView.reusable.dequeueReusableView(ListView>.self, for: indexPath) result.view.summaryView.configure(startAt: startAt, title: title) return result.cell case let .subTextSecondBasic(title, broadcastTime): let result = collectionView.reusable.dequeueReusableView(ListView>.self, for: indexPath) result.view.summaryView.configure(title: title, broadcastTime: broadcastTime) return result.cell ܕύϥϝʔλͷύλʔϯ͝ͱʹEFRVFVF͢Δ͜ͱʹ ͳΓɺϨΠΞ΢τ͕֬ఆͨ͠ঢ়ଶͰඞཁͳ෦෼ͷΈΛ ඞཁͳϝιου͚ͩݺͼग़ͯ͠ߋ৽Ͱ͖Δ

Slide 45

Slide 45 text

os_signpostͰcon fi gureͷॲཧ࣌ؒΛൺֱ

Slide 46

Slide 46 text

os_signpostͰcon fi gureͷॲཧ࣌ؒΛൺֱ &OVN 5JNFT 5ZQF1BSBNFUFS 5JNFT 4VC5FYU5PQ#BTJD ЖT ЖT 4VC5FYU4FDPOE#BTJD ЖT ЖT /P4VC5FYU#BTJD NT ЖT 4VC5FYU4FDPOE$PJO NT ЖT /P4VC5FYU$PJO NT ЖT 4VC5FYU5PQ7JFXJOH5ZQF NT ЖT 4VC5FYU4FDPOE7JFXJOH5ZQF NT ЖT /P4VC5FYU7JFXJOH5ZQF NT ЖT

Slide 47

Slide 47 text

os_signpostͰcon fi gureͷॲཧ࣌ؒΛൺֱ &OVN 5JNFT 5ZQF1BSBNFUFS 5JNFT 4VC5FYU5PQ#BTJD ЖT ЖT 4VC5FYU4FDPOE#BTJD ЖT ЖT /P4VC5FYU#BTJD NT ЖT 4VC5FYU4FDPOE$PJO NT ЖT /P4VC5FYU$PJO NT ЖT 4VC5FYU5PQ7JFXJOH5ZQF NT ЖT 4VC5FYU4FDPOE7JFXJOH5ZQF NT ЖT /P4VC5FYU7JFXJOH5ZQF NT ЖT ڞ௨ͷ7JFX࣮૷͍ͯ͠Δͷ͸ಉ͕ͩ͡ɺ$PNCJOBUJPO ͝ͱͷδΣωϦοΫͳܕΛར༻ͯ͠ɺϨΠΞ΢τ͕֬ఆ͠ ͨঢ়ଶͷ7JFXΛར༻Ͱ͖ΔͷͰɺϨΠΞ΢τͷߋ৽͸΄ ΅͞Εͣʹจࣈྻ౳ͷߋ৽ͷΈʹͳΔͨΊ଎͍

Slide 48

Slide 48 text

ຊLTͷιʔείʔυ https://github.com/marty-suzuki/iOSDC2022LightningTalkSample

Slide 49

Slide 49 text

iOSDC Japan 2022 ͝੩ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠📱