$30 off During Our Annual Pro Sale. View Details »

今更だけどUIKitで型パラメータのインジェクトを利用してViewのレイアウトをしてみよう

Taiki Suzuki
September 12, 2022

 今更だけどUIKitで型パラメータのインジェクトを利用してViewのレイアウトをしてみよう

Taiki Suzuki

September 12, 2022
Tweet

More Decks by Taiki Suzuki

Other Decks in Programming

Transcript

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

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

    TR 6-S
  3.   ABEMAͷϦετͷσβΠϯͷύλʔϯ

  4.   σβΠϯͷύλʔϯͷྫ

  5.   σβΠϯͷύλʔϯͷྫ

  6.   σβΠϯͷύλʔϯͷྫ

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

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

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

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

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

  12.   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?) }
  13.   enumͰදݱͨ͠৔߹ͷྫ public enum SummaryCombination: Hashable { case subTextTopBasic(startAt:

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

    String, duration: String, tag: Tag?, coin: Int?) case noSubTextCoin(title: String, coin: Int?, expirationTime: String?)
  15.   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?)
  16.   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): ... } } }
  17.   enumͰදݱͨ͠৔߹ͷྫ final class SummaryView: UIView {

  18.   enumͰදݱͨ͠৔߹ͷྫ final class SummaryView: UIView { titleLabel.text =

    title titleLabel.numberOfLines = 2 configureSubTextLabel(text: startAt, position: 0) additionalView?.removeFromSuperview()
  19.   iOSDC Japan 2022 ܕύϥϝʔλΛར༻͢Δͱ Ͳ͏͍͏࣮૷ʹͳΔ͔

  20.   ܕύϥϝʔλͰදݱͨ͠৔߹ͷྫ 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 } }
  21.   ܕύϥϝʔλͰදݱͨ͠৔߹ͷྫ protocol SummaryRow { static func subTextLabelAndInsertionIndex() ->

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

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

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

    (UILabel, Int)? { nil } }
  25.   ܕύϥϝʔλͰදݱͨ͠৔߹ͷྫ 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) } }
  26.   ܕύϥϝʔλͰදݱͨ͠৔߹ͷྫ protocol SummaryColumn { associatedtype AdditionalView: UIView static

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

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

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

    TagTextView? { TagTextView(frame: .zero) } }
  30.   ܕύϥϝʔλͰදݱͨ͠৔߹ͷྫ protocol SummaryCombination { associatedtype Row: SummaryRow associatedtype

    Column: SummaryColumn associatedtype AdditionalView: UIView static func numberOfTitleLines() -> Int static func additionalView() -> AdditionalView? static func subTextColor() -> UIColor? } ߦͱྻͷ૊Έ߹ΘͤΛදݱ͢ΔQSPUPDPM
  31.   ܕύϥϝʔλͰදݱͨ͠৔߹ͷྫ 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() } }
  32.   ܕύϥϝʔλͰදݱͨ͠৔߹ͷྫ enum SubTextTopBasic: SummaryCombination { typealias Row =

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

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

    SubTextTop typealias Column = ViewingType static func numberOfTitleLines() -> Int { 2 } static func additionalView() -> Column.AdditionalView? { Column.additionalView() } }
  35.   ܕύϥϝʔλΛར༻ͨ͠ڞ௨ͷViewͷྫ final class SummaryView<Combination: SummaryCombination>: 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) } } }
  36.   ܕύϥϝʔλΛར༻ͨ͠ڞ௨ͷViewͷྫ final class SummaryView<Combination: SummaryCombination>: 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͔Β͸ґଘΛ஫ೖ͢Δ͜ͱ͸Ͱ͖ ͳ͍͕ɺܕύϥϝʔλ͔Β஫ೖ͢Δ͜ͱ͕Ͱ͖Δ
  37.   ܕύϥϝʔλΛར༻ͨ͠ڞ௨ͷViewͷྫ final class SummaryView<Combination: SummaryCombination>: UIView { private

    let titleLabel: UILabel titleLabel.numberOfLines = Combination.numberOfTitleLines() stackView.addArrangedSubview(titleLabel)
  38.   ܕύϥϝʔλΛར༻ͨ͠ڞ௨ͷViewͷྫ final class SummaryView<Combination: SummaryCombination>: 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) }
  39.   ܕύϥϝʔλΛར༻ͨ͠ڞ௨ͷViewͷྫ final class SummaryView<Combination: SummaryCombination>: UIView { private

    let additionalView: Combination.AdditionalView? self.additionalView = Combination.additionalView() if let additionalView = additionalView { stackView.addArrangedSubview(additionalView) } BTTPDJBUFE5ZQFͰܕΛఆ͍ٛͯ͠ΔͷͰɺར༻ ઌͰΩϟετͤͣʹΧελϜ7JFXͷϝιου౳ʹ ΞΫηεͰ͖Δ
  40.   ܕύϥϝʔλΛར༻ͨ͠ڞ௨ͷ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) } }
  41.   ܕύϥϝʔλΛར༻ͨ͠ڞ௨ͷ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ͷ࣮૷Λݺͼग़ ͯ͠ϨΠΞ΢τΛߋ৽͢Δ
  42.   enumͱܕύϥϝʔλͷύλʔϯͰUICollectionViewͷdequeueͷҧ͍ // enumͰදݱͨ͠৔߹ public func collectionView(_ collectionView: UICollectionView,

    cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let combination = combinations[indexPath.row] let result = collectionView.reusable.dequeueReusableView(ListView<SummaryView>.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<SummaryView<SubTextTopBasic>>.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<SummaryView<SubTextSecondBasic>>.self, for: indexPath) result.view.summaryView.configure(title: title, broadcastTime: broadcastTime) return result.cell ... } }
  43.   enumͱܕύϥϝʔλͷύλʔϯͰUICollectionViewͷdequeueͷҧ͍ // enumͰදݱͨ͠৔߹ let result = collectionView.reusable.dequeueReusableView(ListView<SummaryView>.self, for:

    indexPath) result.view.summaryView.configure(combination) return result.cell ಉҰͷ7JFXΛ࢖͍ճͯ͠ɺFOVNͷDBTFʹΑͬͯ ಺෦ͰϨΠΞ΢τΛมߋ͍ͯ͠Δ
  44.   enumͱܕύϥϝʔλͷύλʔϯͰUICollectionViewͷdequeueͷҧ͍ // enumͰදݱͨ͠৔߹ switch viewDataModels[indexPath.row] { case let

    .subTextTopBasic(startAt, title): let result = collectionView.reusable.dequeueReusableView(ListView<SummaryView<SubTextTopBasic>>.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<SummaryView<SubTextSecondBasic>>.self, for: indexPath) result.view.summaryView.configure(title: title, broadcastTime: broadcastTime) return result.cell ܕύϥϝʔλͷύλʔϯ͝ͱʹEFRVFVF͢Δ͜ͱʹ ͳΓɺϨΠΞ΢τ͕֬ఆͨ͠ঢ়ଶͰඞཁͳ෦෼ͷΈΛ ඞཁͳϝιου͚ͩݺͼग़ͯ͠ߋ৽Ͱ͖Δ
  45.   os_signpostͰcon fi gureͷॲཧ࣌ؒΛൺֱ

  46.   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 
  47.   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Λར༻Ͱ͖ΔͷͰɺϨΠΞ΢τͷߋ৽͸΄ ΅͞Εͣʹจࣈྻ౳ͷߋ৽ͷΈʹͳΔͨΊ଎͍
  48.   ຊLTͷιʔείʔυ https://github.com/marty-suzuki/iOSDC2022LightningTalkSample

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