美文网首页iglistkit
IGListKit框架详细解析(四) —— 基于IGListKi

IGListKit框架详细解析(四) —— 基于IGListKi

作者: 刀客传奇 | 来源:发表于2019-01-19 17:21 被阅读90次

    版本记录

    版本号 时间
    V1.0 2019.01.19 星期六

    前言

    IGListKit这个框架可能很多人没有听过,它其实就是一个数据驱动的UICollectionView框架,用于构建快速灵活的列表。它由Instagram开发,接下来这几篇我们就一起看一下这个框架。感兴趣的看上面几篇。
    1. IGListKit框架详细解析(一) —— 基本概览(一)
    2. IGListKit框架详细解析(二) —— 基于IGListKit框架的更好的UICollectionViews简单示例(一)
    3. IGListKit框架详细解析(三) —— 基于IGListKit框架的更好的UICollectionViews简单示例(二)

    源码

    1. Swift

    首先看下工程组织结构

    接下来就是代码了

    1. AppDelegate.swift
    
    import UIKit
    
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
      var window: UIWindow?
      
      func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        window = UIWindow(frame: UIScreen.main.bounds)
        window?.backgroundColor = .black
        let nav = UINavigationController(navigationBarClass: CustomNavigationBar.self, toolbarClass: nil)
        nav.pushViewController(FeedViewController(), animated: false)
        window?.rootViewController = nav
        window?.makeKeyAndVisible()
        return true
      }
    }
    
    2. DateSortable.swift
    
    import Foundation
    
    protocol DateSortable {
      var date: Date { get }
    }
    
    3. JournalEntry.swift
    
    import Foundation
    
    class JournalEntry: NSObject, DateSortable {
      let date: Date
      let text: String
      let user: User
      
      init(date: Date, text: String, user: User) {
        self.date = date
        self.text = text
        self.user = user
      }
    }
    
    4. Message.swift
    
    import UIKit
    
    class Message: NSObject, DateSortable {
      let date: Date
      let text: String
      let user: User
      
      init(date: Date, text: String, user: User) {
        self.date = date
        self.text = text
        self.user = user
      }
    }
    
    5. SolFormatter.swift
    
    import Foundation
    
    struct SolFormatter {
      let landingDate: Date
      
      init(landingDate: Date = Date(timeIntervalSinceNow: -31725960)) {
        self.landingDate = landingDate
      }
      
      func sols(fromDate date: Date) -> Int {
        let martianDay: TimeInterval = 1477 * 60 // 24h37m
        let seconds = date.timeIntervalSince(landingDate)
        return lround(seconds / martianDay)
      }
    }
    
    6. User.swift
    
    import Foundation
    
    class User: NSObject {
      let id: Int
      let name: String
      
      init(id: Int, name: String) {
        self.id = id
        self.name = name
      }
    }
    
    7. Weather.swift
    
    import UIKit
    
    enum WeatherCondition: String {
      case cloudy = "Cloudy"
      case sunny = "Sunny"
      case partlyCloudy = "Partly Cloudy"
      case dustStorm = "Dust Storm"
      
      var emoji: String {
        switch self {
        case .cloudy: return "☁️"
        case .sunny: return "☀️"
        case .partlyCloudy: return "⛅️"
        case .dustStorm: return "🌪"
        }
      }
    }
    
    class Weather: NSObject {
      let temperature: Int
      let high: Int
      let low: Int
      let date: Date
      let sunrise: String
      let sunset: String
      let condition: WeatherCondition
      
      init(
        temperature: Int,
        high: Int,
        low: Int,
        date: Date,
        sunrise: String,
        sunset: String,
        condition: WeatherCondition
        ) {
        self.temperature = temperature
        self.high = high
        self.low = low
        self.date = date
        self.sunrise = sunrise
        self.sunset = sunset
        self.condition = condition
      }
    }
    
    8. NSObject+ListDiffable.swift
    
    import Foundation
    import IGListKit
    
    // MARK: - ListDiffable
    extension NSObject: ListDiffable {
      public func diffIdentifier() -> NSObjectProtocol {
        return self
      }
    
      public func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
        return isEqual(object)
      }
    }
    
    9. JournalSectionController.swift
    
    import IGListKit
    
    class JournalSectionController: ListSectionController {
      var entry: JournalEntry!
      let solFormatter = SolFormatter()
    
      override init() {
        super.init()
        inset = UIEdgeInsets(top: 0, left: 0, bottom: 15, right: 0)
      }
    
    }
    
    // MARK: - Data Provider
    extension JournalSectionController {
      override func numberOfItems() -> Int {
        return 2
      }
      
      override func sizeForItem(at index: Int) -> CGSize {
        guard
          let context = collectionContext,
          let entry = entry
          else {
            return .zero
        }
        let width = context.containerSize.width
        
        if index == 0 {
          return CGSize(width: width, height: 30)
        } else {
          return JournalEntryCell.cellSize(width: width, text: entry.text)
        }
    
      }
      
      override func cellForItem(at index: Int) -> UICollectionViewCell {
        let cellClass: AnyClass = index == 0 ? JournalEntryDateCell.self : JournalEntryCell.self
        let cell = collectionContext!.dequeueReusableCell(of: cellClass, for: self, at: index)
        
        if let cell = cell as? JournalEntryDateCell {
          cell.label.text = "SOL \(solFormatter.sols(fromDate: entry.date))"
        } else if let cell = cell as? JournalEntryCell {
          cell.label.text = entry.text
        }
        return cell
    
      }
      
      override func didUpdate(to object: Any) {
        entry = object as? JournalEntry
      }
    }
    
    10. MessageSectionController.swift
    
    import IGListKit
    
    class MessageSectionController: ListSectionController {
      var message: Message!
      
      override init() {
        super.init()
        inset = UIEdgeInsets(top: 0, left: 0, bottom: 15, right: 0)
      }
    }
    
    // MARK: - Data Provider
    extension MessageSectionController {
      override func numberOfItems() -> Int {
        return 1
      }
      
      override func sizeForItem(at index: Int) -> CGSize {
        guard
          let context = collectionContext,
          let message = message
          else {
            return .zero
        }
        return MessageCell.cellSize(width: context.containerSize.width, text: message.text)
      }
      
      override func cellForItem(at index: Int) -> UICollectionViewCell {
        let cell = collectionContext?.dequeueReusableCell(of: MessageCell.self, for: self, at: index) as! MessageCell
        cell.messageLabel.text = message.text
        cell.titleLabel.text = message.user.name.uppercased()
        
        return cell
      }
      
      override func didUpdate(to object: Any) {
        message = object as? Message
      }
    }
    
    11. WeatherSectionController.swift
    
    import IGListKit
    
    class WeatherSectionController: ListSectionController {
      var weather: Weather!
      var expanded = false
      
      override init() {
        super.init()
        inset = UIEdgeInsets(top: 0, left: 0, bottom: 15, right: 0)
      }
    }
    
    // MARK: - Data Provider
    extension WeatherSectionController {
      override func didUpdate(to object: Any) {
        weather = object as? Weather
      }
      
      override func numberOfItems() -> Int {
        return expanded ? 5 : 1
      }
      
      override func sizeForItem(at index: Int) -> CGSize {
        guard let context = collectionContext else {
          return .zero
        }
        let width = context.containerSize.width
        if index == 0 {
          return CGSize(width: width, height: 70)
        } else {
          return CGSize(width: width, height: 40)
        }
      }
      
      override func cellForItem(at index: Int) -> UICollectionViewCell {
        let cellClass: AnyClass = index == 0 ? WeatherSummaryCell.self : WeatherDetailCell.self
        let cell = collectionContext!.dequeueReusableCell(of: cellClass, for: self, at: index)
        
        if let cell = cell as? WeatherSummaryCell {
          cell.setExpanded(expanded)
        } else if let cell = cell as? WeatherDetailCell {
          let title: String, detail: String
          switch index {
          case 1:
            title = "SUNRISE"
            detail = weather.sunrise
          case 2:
            title = "SUNSET"
            detail = weather.sunset
          case 3:
            title = "HIGH"
            detail = "\(weather.high) C"
          case 4:
            title = "LOW"
            detail = "\(weather.low) C"
          default:
            title = "n/a"
            detail = "n/a"
          }
          cell.titleLabel.text = title
          cell.detailLabel.text = detail
        }
        return cell
      }
      
      override func didSelectItem(at index: Int) {
        collectionContext?.performBatch(animated: true, updates: { batchContext in
          self.expanded.toggle()
          batchContext.reload(self)
        }, completion: nil)
      }
    }
    
    12. JournalLoader.swift
    
    import Foundation
    
    class JournalEntryLoader {
      var entries: [JournalEntry] = []
      
      func loadLatest() {
        let user = User(id: 1, name: "Mark Watney")
        let entries = [
          JournalEntry(
            date: Date(timeIntervalSinceNow: -1727283),
            text: "Ok I think I have this potato thing figured out. I'm using some of the leftover fuel from the landing thruster and basically lighting it on fire. The hydrogen and oxygen combine to make water. If I throttle the reaction I can let this run all day and generate enough water in the air to hydrate my potatos.\n\nThough, I'm basically igniting jet fuel in my living room.",
            user: user
          ),
          JournalEntry(
            date: Date(timeIntervalSinceNow: -1382400),
            text: "I blew up.\n\nMy potato hydration system was working perfectly, but I forgot to account for excess oxygen from the reaction. I ended up with 30% pure oxygen in the HAB. Where I'm making mini explosions. Oh did I mention I live here?\n\nI survived but the HAB is basically gone, along with all my potatos. The cold air instantly froze the ones I have, so there's that at least.",
            user: user
          ),
          JournalEntry(
            date: Date(timeIntervalSinceNow: -823200),
            text: "I figured out how to communicate with NASA! Years ago we sent a small probe called Pathfinder to Mars to poke at the sand a bit. The little rover only lasted a couple months, but I found it! All I had to do was swap the batteries and its as good as new.\n\nWith all this in place I can send pictures to NASA, maybe Johansen can tell me how to hack this thing?",
            user: user
          ),
          JournalEntry(
            date: Date(timeIntervalSinceNow: -259200),
            text: "Alright, its time for me to leave the HAB and make the several-thousand kilometer trek to the next landing site. The MAV is already there, so I'm going to try to launch this thing and intercept with Hermes. Sounds crazy, right?\n\nBut it's the last chance I've got.",
            user: user
          )
        ]
        self.entries = entries
      }
    }
    
    13. Pathfinder.swift
    
    import Foundation
    
    protocol PathfinderDelegate: class {
      func pathfinderDidUpdateMessages(pathfinder: Pathfinder)
    }
    
    private func delay(time: Double = 1, execute work: @escaping @convention(block) () -> Swift.Void) {
      DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + time) {
        work()
      }
    }
    
    private func lewisMessage(text: String, interval: TimeInterval = 0) -> Message {
      let user = User(id: 2, name: "cpt.lewis")
      return Message(date: Date(timeIntervalSinceNow: interval), text: text, user: user)
    }
    
    class Pathfinder {
      weak var delegate: PathfinderDelegate?
      
      var messages: [Message] = {
        var arr: [Message] = []
        arr.append(lewisMessage(text: "Mark, are you receiving me?", interval: -803200))
        arr.append(lewisMessage(text: "I think I left behind some ABBA, might help with the drive 😜", interval: -259200))
        return arr
      }() {
        didSet {
          delegate?.pathfinderDidUpdateMessages(pathfinder: self)
        }
      }
      
      func connect() {
        delay(time: 2.3) {
          self.messages.append(lewisMessage(text: "Liftoff in 3..."))
          delay {
            self.messages.append(lewisMessage(text: "2..."))
            delay {
              self.messages.append(lewisMessage(text: "1..."))
            }
          }
        }
      }
    }
    
    14. TextSize.swift
    
    import UIKit
    
    public struct TextSize {
      private struct CacheEntry: Hashable, Equatable {
        let text: String
        let font: UIFont
        let width: CGFloat
        let insets: UIEdgeInsets
        
        func hash(into hasher: inout Hasher) {
          hasher.combine(text)
          hasher.combine(width)
          hasher.combine(insets.top)
          hasher.combine(insets.left)
          hasher.combine(insets.bottom)
          hasher.combine(insets.right)
        }
        
        static func ==(lhs: TextSize.CacheEntry, rhs: TextSize.CacheEntry) -> Bool {
          return lhs.width == rhs.width && lhs.insets == rhs.insets && lhs.text == rhs.text
        }
      }
      
      private static var cache: [CacheEntry: CGRect] = [:] {
        didSet {
          assert(Thread.isMainThread)
        }
      }
      
      public static func size(_ text: String, font: UIFont, width: CGFloat, insets: UIEdgeInsets = .zero) -> CGRect {
        let key = CacheEntry(text: text, font: font, width: width, insets: insets)
        if let hit = cache[key] {
          return hit
        }
        
        let constrainedSize = CGSize(width: width - insets.left - insets.right, height: .greatestFiniteMagnitude)
        let attributes = [NSAttributedString.Key.font: font]
        let options: NSStringDrawingOptions = [.usesFontLeading, .usesLineFragmentOrigin]
        var bounds = (text as NSString).boundingRect(with: constrainedSize, options: options, attributes: attributes, context: nil)
        bounds.size.width = width
        bounds.size.height = ceil(bounds.height + insets.top + insets.bottom)
        cache[key] = bounds
        return bounds
      }
    }
    
    15. Theme.swift
    
    import UIKit
    
    extension UIColor {
      // https://github.com/yeahdongcn/UIColor-Hex-Swift/blob/master/HEXColor/UIColorExtension.swift
      public convenience init(hex6: UInt32, alpha: CGFloat = 1) {
        let divisor = CGFloat(255)
        let red     = CGFloat((hex6 & 0xFF0000) >> 16) / divisor
        let green   = CGFloat((hex6 & 0x00FF00) >>  8) / divisor
        let blue    = CGFloat( hex6 & 0x0000FF       ) / divisor
        self.init(red: red, green: green, blue: blue, alpha: alpha)
      }
    }
    
    let CommonInsets = UIEdgeInsets(top: 8, left: 15, bottom: 8, right: 15)
    
    func AppFont(size: CGFloat = 18) -> UIFont {
      return UIFont(name: "OCRAStd", size: size)!
    }
    
    16. WxScanner.swift
    
    import Foundation
    
    class WxScanner {
      let currentWeather = Weather(
        temperature: 6,
        high: 13,
        low: -69,
        date: Date(),
        sunrise: "05:42",
        sunset: "17:58",
        condition: .dustStorm
      )
    }
    
    17. ClassicFeedViewController.swift
    
    import UIKit
    
    class ClassicFeedViewController: UIViewController {
      let loader = JournalEntryLoader()
      let solFormatter = SolFormatter()
      
      let collectionView: UICollectionView = {
        let layout = UICollectionViewFlowLayout()
        layout.minimumLineSpacing = 0
        layout.minimumInteritemSpacing = 0
        layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 15, right: 0)
        let view = UICollectionView(frame: .zero, collectionViewLayout: layout)
        view.backgroundColor = .black
        view.alwaysBounceVertical = true
        return view
      }()
      
      override func viewDidLoad() {
        super.viewDidLoad()
        
        collectionView.register(JournalEntryCell.self, forCellWithReuseIdentifier: "JournalEntryCell")
        collectionView.register(JournalEntryDateCell.self, forCellWithReuseIdentifier: "JournalEntryDateCell")
        collectionView.dataSource = self
        collectionView.delegate = self
        view.addSubview(collectionView)
        
        loader.loadLatest()
      }
      
      override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        collectionView.frame = view.bounds
      }
    }
    
    // MARK: - UICollectionViewDataSource
    extension ClassicFeedViewController: UICollectionViewDataSource {
      func numberOfSections(in collectionView: UICollectionView) -> Int {
        return loader.entries.count
      }
      
      func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 2
      }
      
      func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let identifier = indexPath.item == 0 ? "JournalEntryDateCell" : "JournalEntryCell"
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath)
        let entry = loader.entries[indexPath.section]
        if let cell = cell as? JournalEntryDateCell {
          cell.label.text = "SOL \(solFormatter.sols(fromDate: entry.date))"
        } else if let cell = cell as? JournalEntryCell {
          cell.label.text = entry.text
        }
        return cell
      }
    }
    
    // MARK: - UICollectionViewDelegateFlowLayout
    extension ClassicFeedViewController: UICollectionViewDelegateFlowLayout {
      func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let width = collectionView.bounds.width
        if indexPath.item == 0 {
          return CGSize(width: width, height: 30)
        } else {
          let entry = loader.entries[indexPath.section]
          return JournalEntryCell.cellSize(width: width, text: entry.text)
        }
      }
    }
    
    18. FeedViewController.swift
    
    import UIKit
    import IGListKit
    
    class FeedViewController: UIViewController {
      let loader = JournalEntryLoader()
      let collectionView: UICollectionView = {
        let view = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
        view.backgroundColor = .black
        return view
      }()
      lazy var adapter: ListAdapter = {
        return ListAdapter(updater: ListAdapterUpdater(), viewController: self, workingRangeSize: 0)
      }()
      let pathfinder = Pathfinder()
      let wxScanner = WxScanner()
      
      override func viewDidLoad() {
        super.viewDidLoad()
        
        loader.loadLatest()
        view.addSubview(collectionView)
        adapter.collectionView = collectionView
        adapter.dataSource = self
        pathfinder.delegate = self
        pathfinder.connect()
      }
      
      override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        collectionView.frame = view.bounds
      }
    }
    
    // MARK: - ListAdapterDataSource
    extension FeedViewController: ListAdapterDataSource {
      func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
        var items: [ListDiffable] = [wxScanner.currentWeather]
        items += loader.entries as [ListDiffable]
        items += pathfinder.messages as [ListDiffable]
    
        return items.sorted { (left: Any, right: Any) -> Bool in
          guard let left = left as? DateSortable, let right = right as? DateSortable else {
            return false
          }
          return left.date > right.date
        }
      }
      
      func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
        if object is Message {
          return MessageSectionController()
        } else if object is Weather {
          return WeatherSectionController()
        } else {
          return JournalSectionController()
        }
      }
      
      func emptyView(for listAdapter: ListAdapter) -> UIView? {
        return nil
      }
    }
    
    // MARK: - PathfinderDelegate
    extension FeedViewController: PathfinderDelegate {
      func pathfinderDidUpdateMessages(pathfinder: Pathfinder) {
        adapter.performUpdates(animated: true)
      }
    }
    
    19. CustomNavigationBar.swift
    
    import UIKit
    
    class CustomNavigationBar: UINavigationBar {
      let titleLabel: UILabel = {
        let label = UILabel()
        label.backgroundColor = .clear
        label.text = "MARSLINK"
        label.font = AppFont()
        label.textAlignment = .center
        label.textColor = .white
        return label
      }()
      
      let statusLabel: UILabel = {
        let label = UILabel()
        label.backgroundColor = .clear
        label.text = "RECEIVING"
        label.font = AppFont(size: 13)
        label.textAlignment = .center
        label.textColor = UIColor(hex6: 0x42c84b)
        label.sizeToFit()
        return label
      }()
      
      let statusIndicator: CAShapeLayer = {
        let layer = CAShapeLayer()
        layer.strokeColor = UIColor.white.cgColor
        layer.lineWidth = 1
        layer.fillColor = UIColor.black.cgColor
        let size: CGFloat = 8
        let frame = CGRect(x: 0, y: 0, width: size, height: size)
        layer.path = UIBezierPath(roundedRect: frame, cornerRadius: size / 2).cgPath
        layer.frame = frame
        return layer
      }()
      
      let highlightLayer: CAShapeLayer = {
        let layer = CAShapeLayer()
        layer.fillColor = UIColor(hex6: 0x76879D).cgColor
        return layer
      }()
    
      var statusOn = false
      
      override init(frame: CGRect) {
        super.init(frame: frame)
        layer.addSublayer(highlightLayer)
        layer.addSublayer(statusIndicator)
        addSubview(titleLabel)
        addSubview(statusLabel)
        barTintColor = .black
        updateStatus()
      }
      
      required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
      }
      
      override func layoutSubviews() {
        super.layoutSubviews()
        let titleWidth: CGFloat = 130
        let borderHeight: CGFloat = 4
        
        let path = UIBezierPath()
        path.move(to: .zero)
        path.addLine(to: CGPoint(x: titleWidth, y: 0))
        path.addLine(to: CGPoint(x: titleWidth, y: bounds.height - borderHeight))
        path.addLine(to: CGPoint(x: bounds.width, y: bounds.height - borderHeight))
        path.addLine(to: CGPoint(x: bounds.width, y: bounds.height))
        path.addLine(to: CGPoint(x: 0, y: bounds.height))
        path.close()
        highlightLayer.path = path.cgPath
        
        titleLabel.frame = CGRect(x: 0, y: 0, width: titleWidth, height: bounds.height)
        statusLabel.frame = CGRect(
          x: bounds.width - statusLabel.bounds.width - CommonInsets.right,
          y: bounds.height - borderHeight - statusLabel.bounds.height - 6,
          width: statusLabel.bounds.width,
          height: statusLabel.bounds.height
        )
        statusIndicator.position = CGPoint(x: statusLabel.center.x - 50, y: statusLabel.center.y - 1)
      }
      
      func updateStatus() {
        statusOn.toggle()
        CATransaction.begin()
        CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
        statusIndicator.fillColor = (statusOn ? UIColor.white : UIColor.black).cgColor
        CATransaction.commit()
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.6) {
          self.updateStatus()
        }
      }
    
    }
    
    20. JournalEntryCell.swift
    
    import UIKit
    
    class JournalEntryCell: UICollectionViewCell {
      static let font = AppFont()
      static let inset = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 15)
      
      static func cellSize(width: CGFloat, text: String) -> CGSize {
        return TextSize.size(text, font: JournalEntryCell.font, width: width, insets: JournalEntryCell.inset).size
      }
      
      let label: UILabel = {
        let label = UILabel()
        label.backgroundColor = .clear
        label.numberOfLines = 0
        label.font = JournalEntryCell.font
        label.textColor = .white
        return label
      }()
      
      override init(frame: CGRect) {
        super.init(frame: frame)
        contentView.backgroundColor = UIColor(hex6: 0x0c1f3f)
        contentView.addSubview(label)
      }
      
      required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
      }
      
      override func layoutSubviews() {
        super.layoutSubviews()
        label.frame = bounds.inset(by: JournalEntryCell.inset)
      }
      
    }
    
    21. JournalEntryDateCell.swift
    
    import UIKit
    
    class JournalEntryDateCell: UICollectionViewCell {
      let label: UILabel = {
        let label = UILabel()
        label.backgroundColor = .clear
        label.font = AppFont(size: 14)
        label.textColor = UIColor(hex6: 0x42c84b)
        return label
      }()
      
      override init(frame: CGRect) {
        super.init(frame: frame)
        contentView.backgroundColor = UIColor(hex6: 0x0c1f3f)
        contentView.addSubview(label)
      }
      
      required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
      }
      
      override func layoutSubviews() {
        super.layoutSubviews()
        let padding = CommonInsets
        label.frame = bounds.inset(by: UIEdgeInsets(top: 0, left: padding.left, bottom: 0, right: padding.right))
      }
    }
    
    22. MessageCell.swift
    
    import UIKit
    
    class MessageCell: UICollectionViewCell {
      static let titleHeight: CGFloat = 30
      static let font = AppFont()
      
      static func cellSize(width: CGFloat, text: String) -> CGSize {
        let labelBounds = TextSize.size(text, font: MessageCell.font, width: width, insets: CommonInsets)
        return CGSize(width: width, height: labelBounds.height + MessageCell.titleHeight)
      }
      
      let messageLabel: UILabel = {
        let label = UILabel()
        label.backgroundColor = .clear
        label.numberOfLines = 0
        label.font = MessageCell.font
        label.textColor = .white
        return label
      }()
      
      let titleLabel: UILabel = {
        let label = UILabel()
        label.backgroundColor = .clear
        label.font = AppFont(size: 14)
        label.textColor = UIColor(hex6: 0x42c84b)
        return label
      }()
      
      let statusLabel: UILabel = {
        let label = UILabel()
        label.layer.borderColor = UIColor(hex6: 0x76879d).cgColor
        label.layer.borderWidth = 1
        label.backgroundColor = .clear
        label.font = AppFont(size: 8)
        label.textColor = UIColor(hex6: 0x76879d)
        label.textAlignment = .center
        label.text = "NEW MESSAGE"
        return label
      }()
      
      override init(frame: CGRect) {
        super.init(frame: frame)
        contentView.backgroundColor = UIColor(hex6: 0x0c1f3f)
        contentView.addSubview(messageLabel)
        contentView.addSubview(titleLabel)
        contentView.addSubview(statusLabel)
      }
      
      required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
      }
      
      override func layoutSubviews() {
        super.layoutSubviews()
        titleLabel.frame = CGRect(x: CommonInsets.left, y: 0, width: bounds.width - CommonInsets.left - CommonInsets.right, height: MessageCell.titleHeight)
        statusLabel.frame = CGRect(x: bounds.width - 80, y: 4, width: 70, height: 18)
        let messageFrame = CGRect(x: 0, y: titleLabel.frame.maxY, width: bounds.width, height: bounds.height - MessageCell.titleHeight)
        messageLabel.frame = messageFrame.inset(by: CommonInsets)
      }
    }
    
    23. WeatherDetailCell.swift
    
    import UIKit
    
    class WeatherDetailCell: UICollectionViewCell {
      let titleLabel: UILabel = {
        let label = UILabel()
        label.backgroundColor = .clear
        label.font = AppFont()
        label.textColor = UIColor(hex6: 0x42c84b)
        return label
      }()
      
      let detailLabel: UILabel = {
        let label = UILabel()
        label.backgroundColor = .clear
        label.font = AppFont()
        label.textColor = UIColor(hex6: 0x42c84b)
        label.textAlignment = .right
        return label
      }()
      
      override init(frame: CGRect) {
        super.init(frame: frame)
        contentView.addSubview(titleLabel)
        contentView.addSubview(detailLabel)
        contentView.backgroundColor = UIColor(hex6: 0x0c1f3f)
      }
      
      required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
      }
      
      override func layoutSubviews() {
        super.layoutSubviews()
        let insetBounds = bounds.inset(by: CommonInsets)
        titleLabel.frame = insetBounds
        detailLabel.frame = insetBounds
      }
    }
    
    24. WeatherSummaryCell.swift
    
    import UIKit
    
    class WeatherSummaryCell: UICollectionViewCell {
      private let expandLabel: UILabel = {
        let label = UILabel()
        label.backgroundColor = .clear
        label.font = AppFont(size: 30)
        label.textColor = UIColor(hex6: 0x44758b)
        label.textAlignment = .center
        label.text = ">>"
        label.sizeToFit()
        return label
      }()
      
      let titleLabel: UILabel = {
        let label = UILabel()
        label.backgroundColor = .clear
        label.numberOfLines = 0
        
        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.paragraphSpacing = 4
        let subtitleAttributes = [
          NSAttributedString.Key.font: AppFont(size: 14),
          NSAttributedString.Key.foregroundColor: UIColor(hex6: 0x42c84b),
          NSAttributedString.Key.paragraphStyle: paragraphStyle
        ]
        let titleAttributes = [
          NSAttributedString.Key.font: AppFont(size: 24),
          NSAttributedString.Key.foregroundColor: UIColor.white
        ]
        let attributedText = NSMutableAttributedString(string: "LATEST\n", attributes: subtitleAttributes)
        attributedText.append(NSAttributedString(string: "WEATHER", attributes: titleAttributes))
        label.attributedText = attributedText
        label.sizeToFit()
        
        return label
      }()
      
      func setExpanded(_ expanded: Bool) {
        expandLabel.transform = expanded ? CGAffineTransform(rotationAngle: CGFloat.pi / 2) : .identity
      }
      
      override init(frame: CGRect) {
        super.init(frame: frame)
        contentView.addSubview(expandLabel)
        contentView.addSubview(titleLabel)
        contentView.backgroundColor = UIColor(hex6: 0x0c1f3f)
      }
      
      required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
      }
      
      override func layoutSubviews() {
        super.layoutSubviews()
        let insets = CommonInsets
        titleLabel.frame = CGRect(x: insets.left, y: 0, width: titleLabel.bounds.width, height: bounds.height)
        expandLabel.center = CGPoint(x: bounds.width - expandLabel.bounds.width / 2 - insets.right, y: bounds.height / 2)
      }
    }
    

    后记

    本篇主要简单介绍了基于IGListKit框架的更好的UICollectionViews简单示例,感兴趣的给个赞或者关注~~~

    相关文章

      网友评论

        本文标题:IGListKit框架详细解析(四) —— 基于IGListKi

        本文链接:https://www.haomeiwen.com/subject/rcezdqtx.html