A Swift mixin for reusing views easily and in a type-safe way (UITableViewCells, UICollectionViewCells, custom UIViews, ViewControllers, Storyboards…)

Overview

Reusable

Reusable

A Swift mixin to use UITableViewCells, UICollectionViewCells and UIViewControllers in a type-safe way, without the need to manipulate their String-typed reuseIdentifiers. This library also supports arbitrary UIView to be loaded via a XIB using a simple call to loadFromNib()

CircleCI Platform Version Language: Swift 3 Language: Swift 4 Language: Swift 5

Installation

Requirements: which Reusable version to use for each Swift Version?
Swift Version Reusable Version
2.2 & 2.3 2.5.1
3.0 (†) 3.0.0 +
4.0 4.0.2 +
5.0 4.1.0 +

(†) The Reusable 3.0 code also compiles with Swift 4, you'll need 4.0.2+ only if you're using Carthage for integration

Reusable can be integrated to your Xcode projects using one of the following options:

Installation instructions for Swift Package Manager (SPM)

Swift Package Manager is Apple's decentralized dependency manager to integrate libraries to your Swift projects. It is now fully integrated with Xcode 11

To integrate Reusable into your project using SPM, specify it in your Package.swift file:

let package = Package(
    
    dependencies: [
        .package(url: "https://github.com/AliSoftware/Reusable.git", from: "4.1.0"),
    ],
    targets: [
        .target(name: "YourTarget", dependencies: ["Reusable", ])
        
    ]
)
Installation instructions for Carthage

Carthage is a decentralized dependency manager to add pre-built frameworks to your Cocoa application.

To integrate Reusable into your Xcode project using Carthage, specify it in your Cartfile:

github "AliSoftware/Reusable"

Then run carthage update --use-xcframeworks

Installation instructions for CocoaPods

CocoaPods is a dependency manager to automate integration of frameworks to your Swift and Objective-C Cocoa projects.

To integrate Reusable into your Xcode project using Cocoapods, specify it in your Podfile:

pod 'Reusable'

Introduction

This library aims to make it super-easy to create, dequeue and instantiate reusable views anywhere this pattern is used: from the obvious UITableViewCell and UICollectionViewCell to custom UIViews, even supporting UIViewControllers from Storyboards.
All of that simply by marking your classes as conforming to a protocol, without having to add any code, and creating a type-safe API with no more String-based API.

// Example of what Reusable allows you to do
final class MyCustomCell: UITableViewCell, Reusable { /* And that's it! */ }
tableView.register(cellType: MyCustomCell.self)
let cell: MyCustomCell = tableView.dequeueReusableCell(for: indexPath)

This concept, called a Mixin (a protocol with default implementation for all its methods), is explained here in my blog post in details.

Table of Contents


Type-safe UITableViewCell / UICollectionViewCell

✍️ Examples and explanations below use UITableView and UITableViewCell, but the exact same examples and explanations apply for UICollectionView and UICollectionViewCell.

1. Declare your cells to conform to Reusable or NibReusable

  • Use the Reusable protocol if they don't depend on a NIB (this will use registerClass(…) to register the cell)
  • Use the NibReusable typealias (= Reusable & NibLoadable) if they use a XIB file for their content (this will use registerNib(…) to register the cell)
final class CustomCell: UITableViewCell, Reusable { /* And that's it! */ }

✍️ Notes

  • For cells embedded in a Storyboard's tableView, either one of those two protocols will work (as you won't need to register the cell manually anyway, since registration is handled by the storyboard automatically)
  • If you create a XIB-based cell, don't forget to set its Reuse Identifier field in Interface Builder to the same string as the name of the cell class itself.
  • 💡 NibReusable is a typealias, so you could still use two protocols conformance Reusable, NibLoadable instead of NibReusable.
📑 Example for a Code-based custom tableView cell
final class CodeBasedCustomCell: UITableViewCell, Reusable {
  // By default this cell will have a reuseIdentifier of "CodeBasedCustomCell"
  // unless you provide an alternative implementation of `static var reuseIdentifier`
  
  // No need to add anything to conform to Reusable. You can just keep your normal cell code
  @IBOutlet private weak var label: UILabel!
  func fillWithText(text: String?) { label.text = text }
}
📑 Example for a Nib-based custom tableView cell
final class NibBasedCustomCell: UITableViewCell, NibReusable {
// or
// final class NibBasedCustomCell: UITableViewCell, Reusable, NibLoadable {
  
  // Here we provide a nib for this cell class (which, if we don't override the protocol's
  // default implementation of `static var nib: UINib`, will use a XIB of the same name as the class)
  
  // No need to add anything to conform to Reusable. You can just keep your normal cell code
  @IBOutlet private weak var pictureView: UIImageView!
  func fillWithImage(image: UIImage?) { pictureView.image = image }
}
📑 Example for a Code-based custom collectionView cell
// A UICollectionViewCell which doesn't need a XIB to register
// Either because it's all-code, or because it's registered via Storyboard
final class CodeBasedCollectionViewCell: UICollectionViewCell, Reusable {
  // The rest of the cell code goes here
}
📑 Example for a Nib-based custom collectionView cell
// A UICollectionViewCell using a XIB to define it's UI
// And that will need to register using that XIB
final class NibBasedCollectionViewCell: UICollectionViewCell, NibReusable {
// or
// final class NibBasedCollectionViewCell: UICollectionViewCell, Reusable, NibLoadable {
  
  // The rest of the cell code goes here
  
}

2. Register your cells

Unless you've prototyped your cell in a Storyboard, you'll have to register the cell class or Nib by code.

To do this, instead of calling registerClass(…) or registerNib(…) using a String-based reuseIdentifier, just call:

tableView.register(cellType: theCellClass.self)
📑 Example of `UITableView` registration
class MyViewController: UIViewController {
  @IBOutlet private weak var tableView: UITableView!
  
  override func viewDidLoad() {
    super.viewDidLoad()
    // This will register using the class (via `register(AnyClass?, forCellReuseIdentifier: String)`)
    // because the CodeBasedCustomCell type conforms to Reusable, but not NibLoadable (nor the NibReusable typealias)
    tableView.register(cellType: CodeBasedCustomCell.self)
    // This will register using NibBasedCustomCell.xib (via `register(UINib?, forCellReuseIdentifier: String)`)
    // because the NibBasedCustomCell type conforms to NibLoadable (via the NibReusable typealias)
    tableView.register(cellType: NibBasedCustomCell.self)
  }
}

3. Dequeue your cells

To dequeue a cell (typically in your cellForRowAtIndexPath implementation), simply call dequeueReusableCell(indexPath:):

// Either
let cell = tableView.dequeueReusableCell(for: indexPath) as MyCustomCell
// Or
let cell: MyCustomCell = tableView.dequeueReusableCell(for: indexPath)

As long as Swift can use type-inference to understand that you'll want a cell of type MyCustomCell (either using as MyCustomCell or explicitly typing the receiving variable cell: MyCustomCell), it will magically infer both the cell class to use and thus its reuseIdentifier needed to dequeue the cell, and which exact type to return to save you a type-cast.

  • No need for you to manipulate reuseIdentifiers Strings manually anymore!
  • No need to force-cast the returned UITableViewCell instance down to your MyCustomCell class either!
📑 Example implementation of `cellForRowAtIndexPath` using `Reusable`
extension MyViewController: UITableViewDataSource {
  func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    if indexPath.section == 0 {
      let cell = tableView.dequeueReusableCell(indexPath: indexPath) as CodeBasedCustomCell
      // Customize the cell here. You can call any type-specific methods here without the need for type-casting
      cell.fillWithText("Foo")
      return cell
    } else {
      let cell = tableView.dequeueReusableCell(indexPath: indexPath) as NibBasedCustomCell
      // Customize the cell here. no need to downcasting here either!
      cell.fillWithImage(UIImage(named:"Bar"))
      return cell
    }
  }
}

Now all you have is a beautiful code and type-safe cells, with compile-type checking, and no more String-based API!

💡 If the cell class you want to dequeue is computed at runtime and stored in a variable, you won't be able to use as theVariable or let cell: theVariable obviously. Instead, you can use the optional parameter cellType (which otherwise gets infered by the return type and is thus not necessary to provide explicitly)

📑 Example with a cell type determined at runtime
class ParentCell: UITableViewCell, Reusable {}
class Child1Cell: ParentCell {}
class Child2Cell: ParentCell {}

func cellType(for indexPath: NSIndexPath) -> ParentCell.Type {
  return indexPath.row.isMultiple(of: 2) ? Child1Cell.self : Child2Cell.self
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  let cellClass = self.cellType(for: indexPath)
  // As `self.cellType(for:)` always returns a `ParentCell` (sub-)class, the type
  // of the variable `cell` below is infered to be `ParentCell` too. So only methods
  // declared in the parent `ParentCell` class will be accessible on the `cell` variable.
  // But this code will still dequeue the proper type of cell (Child1Cell or Child2Cell).
  let cell = tableView.dequeueReusableCell(for: indexPath, cellType: cellClass)
  // Then fill the content of your cell (using methods/properties from `ParentCell` type)
  return cell  
}

Type-safe XIB-based reusable views

Reusable also allows you to create reusable custom views designed in Interface Builder to reuse them in other XIBs or Storyboards, or by code. This allows you to treat those views like custom UI widgets that can be used in multiple places in your app.

1. Declare your views to conform to NibLoadable or NibOwnerLoadable

In your swift source declaring your custom view class:

  • Use the NibLoadable protocol if the XIB you're using don't use its "File's Owner" and the reusable view you're designing is the root view of the XIB
  • Use the NibOwnerLoadable protocol if you used a "File's Owner" of the XIB being of the class of your reusable view, and the root view(s) of the XIB is to be set as a subview providing its content.
// a XIB-based custom UIView, used as root of the XIB
final class NibBasedRootView: UIView, NibLoadable { /* and that's it! */ }

// a XIB-based custom UIView, used as the XIB's "File's Owner"
final class NibBasedFileOwnerView: UIView, NibOwnerLoadable { /* and that's it! */ }

💡 You should use the second approach if you plan to use your custom view in another XIB or Storyboard.
This will allow you to just drop a UIView in a XIB/Storyboard and change its class in IB's inspector to the class of your custom XIB-based view to use it. That custom view will then automagically load its own content from the associated XIB when instantiated by the storyboard containing it, without having to write additional code to load the content of the custom view manually every time.

2. Design your view in Interface Builder

For example if you named your class MyCustomWidget and made it NibOwnerLoadable:

  • Set the File's Owner's class to MyCustomWidget
  • Design the content of the view via the root view of that XIB (which is a standard UIView with no custom class) and its subviews
  • Connect any @IBOutlets and @IBActions between the File's Owner (the MyCustomWidget) and its content
🖼 📑 A view configured to be `NibOwnerLoadable`

NibOwnerLoadable view in Interface Builder

final class MyCustomWidget: UIView, NibOwnerLoadable {
  @IBOutlet private var rectView: UIView!
  @IBOutlet private var textLabel: UILabel!

  @IBInspectable var rectColor: UIColor? {
    didSet {
      self.rectView.backgroundColor = self.rectColor
    }
  }
  @IBInspectable var text: String? {
    didSet {
      self.textLabel.text = self.text
    }
  }

}

Then that widget can be integrated in a Storyboard Scene (or any other XIB) by simply dropping a UIView on the Storyboard, and changing its class to MyCustomWidget in IB's inspector.

🖼 Example of a `NibOwnerLoadable` custom view once integrated in another Storyboard
  • In the capture below, all blue square views have a custom class of MyCustomWidget set in Interface Builder.
  • When selecting one of these custom views, you have direct access to all @IBOutlet that this MyCustomWidget exposes, which allows you to connect them to other views of the Storyboard if needed
  • When selecting one of these custom views, you also have access to all the @IBInspectable properties. For example, in the capture below, you can see the "Rect color" and "Text" inspectable properties on the right panel, that you can change right from the Storyboard integrating your custom widget.

NibOwnerLoadable integrated in a Storyboard

3a. Auto-loading the content of a NibOwnerLoadable view

If you used NibOwnerLoadable and made your custom view the File's Owner of your XIB, you should then override init?(coder:) so that it loads it's associated XIB as subviews and add constraints automatically:

final class MyCustomWidget: UIView, NibOwnerLoadable {
  
  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    self.loadNibContent()
  }
}

self.loadNibContent() is a method provided by the NibOwnerLoadable mixin. It basically loads the content from the associated MyCustomWidget.xib, then add all the root views in that XIB as subviews of your MyCustomWidget, with appropriate layout constraints to make them the same size as your MyCustomWidget container view.

Overriding init?(coder:) and calling self.loadNibContent() thus allows you to have that content automatically loaded by the system when that MyCustomWidget in included in another XIB or in a Storyboard (as init?(coder:) is the init that is called by iOS to create those instances in a XIB or Storyboard)

💡 Note: it is also possible to override init(frame:) similarly, in order to be able to also create an instance of that view manually via code if needed.

3b. Instantiating a NibLoadable view

If you used NibLoadable and made your custom view the root view of your XIB (not using the File's Owner at all), these are not designed to be used in other Storyboards or XIBs like NibOwnerLoadable is, as they won't be able to auto-load their content.

Instead, you will instantiate those NibLoadable views by code, which is as simple as calling loadFromNib() on your custom class:

let view1 = NibBasedRootView.loadFromNib() // Create one instance
let view2 = NibBasedRootView.loadFromNib() // Create another one
let view3 = NibBasedRootView.loadFromNib() // and another one

Type-safe ViewControllers from Storyboards

Reusable also allows you to mark your UIViewController classes as StoryboardBased or StoryboardSceneBased to easily instantiate them from their associated Storyboard in a type-safe way.

1. Declare your UIViewController to conform to StoryboardBased or StoryboardSceneBased

In your swift source declaring your custom UIViewController class:

  • Use the StoryboardBased protocol if the *.storyboard file has the same name as the ViewController's class, and its scene is the "initial scene" of the storyboard.
    • This is typically ideal if you use one Storyboard per ViewController, for example.
  • Use the StoryboardSceneBased protocol if scene in your storyboard has the same sceneIdentifier as the name of the ViewController's class, but the *.storyboard file name doesn't necessary match the ViewController's class name.
    • This is typically ideal for secondary scenes in bigger storyboards
    • You'll then be required to implement the sceneStoryboard type property to indicate the storyboard it belongs to.
📑 Example of a ViewController being the initial ViewController of its Storyboard

In this example, CustomVC is designed as the initial ViewController of a Storyboard named CustomVC.storyboard:

final class CustomVC: UIViewController, StoryboardBased { /* and that's it! */ }
📑 Example of a ViewController being an arbitrary scene in a differently-named Storyboard

In this example, SecondaryVC is designed in a Storyboard name CustomVC.storyboard (so with a different name than the class itself) and is not the initial ViewController, but instead has its "Scene Identifier" set to the value "SecondaryVC" (same as the class name)

Conforming to StoryboardSceneBased will still require you to implement static var sceneStoryboard: UIStoryboard { get } to indicate the Storyboard where this scene is designed. You can typically implement that property using a let type constant:

final class SecondaryVC: UIViewController, StoryboardSceneBased {
  static let sceneStoryboard = UIStoryboard(name: "CustomVC", bundle: nil)
  /* and that's it! */
}

2. Instantiate your UIViewControllers

Simply call instantiate() on your custom class. This will automatically know which storyboard to load it from, and which scene (initial or not) to use to instantiate it.

func presentSecondary() {
  let vc = SecondaryVC.instantiate() // Init from the "SecondaryVC" scene of CustomVC.storyboard
  self.present(vc, animated: true) {}
}

Additional tips

Make your subclasses final

I advise you to mark your custom UITableViewCell, UICollectionViewCell, UIView and UIViewController subclasses as being final. This is because:

  • In most cases, the custom cells and VCs you plan to instantiate are not intended to be subclassed themselves.
  • More importantly, it helps the compiler a lot and gives you big optimizations
  • It can be required in some cases when conforming to protocols that have Self requirements, like the ones used by this pod (Reusable, StoryboardBased, …).

In some cases you can avoid making your classes final, but in general it's a good practice, and in the case of this pod, usually your custom UIViewController or whatever won't be subclassed anyway:

  • Either they are intended to be used and instantiated directly and never be subclassed, so final makes sense here
  • In case your custom UIViewController, UITableViewCell, etc… is intended to be subclassed and be the parent class of many classes in your app, it makes more sense to add the protocol conformance (StoryboardBased, Reusable, …) to the child classes (and mark them final) than adding the protocol on the parent, abstract class.

Customize reuseIdentifier, nib, etc for non-conventional uses

The protocols in this pod, like Reusable, NibLoadable, NibOwnerLoadable, StoryboardBased, NibReusable… are what is usually called Mixins, which basically is a Swift protocol with a default implementation provided for all of its methods.

The main benefit is that you don't need to add any code: just conform to Reusable, NibOwnerLoadable or any of those protocol and you're ready to go with no additional code to write.

But of course, those provided implementations are just default implementations. That means that if you need you can still provide your own implementations in case for some reason some of your cells don't follow the classic configuration of using the same name for both the class, the reuseIdentifier and the XIB file.

final class VeryCustomNibBasedCell: UITableViewCell, NibReusable {
  // This cell use a non-standard configuration: its reuseIdentifier and XIB file
  // have a different name as the class itself. So we need to provide a custom implementation or `NibReusable`
  static var reuseIdentifier: String { return "VeryCustomReuseIdentifier" }
  static var nib: UINib { return UINib(nibName: "VeryCustomUI", bundle: nil) } // Use VeryCustomUI.xib
  
  // Then continue with the rest of your normal cell code 
}

The same is true for all the protocols of this pod, which always provide default implementations which could still be replaced by your own if you need some custom cases.

But the beauty is in 90% of cases the default implementation will match typical conventions and the default implementations will be exactly what you want!

Type-safety and fatalError

Reusable allows you to manipulate type-safe APIs and make you avoid typos. But things could still go wrong in case of a misconfguration, for example if you forgot to set the reuseIdentifier of your cell in its XIB, or you declared a FooViewController to be StoryboardBased but forgot to set the initial ViewController flag on that FooViewController scene in that Storyboard, etc.

In such cases, because those are developer errors that should be caught as early as possible in the development process, Reusable will call fatalError with an error message as descriptive as possible (instead of crashing with an obscure message about some force-cast or force-unwrap or whatnot) to help you configure it right.

For example, if Reusable fails to dequeue a cell, it will bail with a message like:

« Failed to dequeue a cell with identifier \(cellType.reuseIdentifier) matching type \(cellType.self). Check that the reuseIdentifier is set properly in your XIB/Storyboard and that you registered the cell beforehand. »

Hopefully, those explicit failure messages will allow you to understand what was misconfigured and help you fix it!


Example Project

This repository comes with an example project in the Example/ folder. Feel free to try it.

It demonstrates how Reusable works for:

  • UITableViewCell and UICollectionViewCell subclasses,
  • Cells whose UI template is either only provided by plain code, or provided by a XIB, or prototyped directly in a Storyboard.
  • UICollectionView's SupplementaryViews (section Headers)
  • Custom UIView designed in a XIB (NibOwnerLoadable)

Talks and Articles about Reusable

The concepts behind Reusable has been presented in various articles and talks:

License

This code is distributed under the MIT license. See the LICENSE file for more info.

Comments
  • Dual Swift 2 & 3 Support

    Dual Swift 2 & 3 Support

    I missed a few API changes in #19. This change addresses those, but also retains Swift 2 support.

    • Added an IdxPath typealias to simplify support for multiple Swift versions (naming suggestions welcome)
    • Had to disable EMBEDDED_CONTENT_CONTAINS_SWIFT in the generated Pods-ReusableDemo target to fix weird errors. Regenerating the Pods project may reintroduce them.

    Note that there are 25 new warnings when building ReusableDemo in Xcode 7.

    ⚠️ Extraneous '' in parameter: 'cellType' has no keyword argument name ⚠️ Extraneous '' in parameter: 'viewType' has no keyword argument name

    I figure warnings in 7 are better than errors in 8 and also don't feel like adding another 25 temporary #if swift(>=3.) ... #endif blocks.

    TODO

    • [x] Travis build matrix
    • [x] Update the README.md to indicate support for Swift 2 & 3
    • [x] Add an entry in the CHANGELOG.md
      • [x] Revert trailing whitespace removal
      • [x] Add trailing whitespace to force wrap line
    • [x] Rename IdxPath -> IndexPath
    • [x] Refactor post_install hook in Podfile to use installer API instead of shelling out to sed
    • [x] Rebase on top of master to bring in changes from v2.5.0
    opened by phatblat 29
  • Increasing safety in loadFromNib

    Increasing safety in loadFromNib

    In NibOwnerLoadable extension there is this dangerous line where it is assumed that a class which inherits from UIView also implements init(frame: CGRect) initializer:

    static func loadFromNib(owner: Self = Self()) -> Self

    and if it does not it will crash in runtime.

    I'm surprised that this even compiles, because according to the Swift documentation:

    subclasses do not inherit their superclass initializers by default

    Write the required modifier before the definition of a class initializer to indicate that every subclass of the class must implement that initializer:

    and since: required init?(coder aDecoder: NSCoder) is the only required initializer in UIView class then it should be the only initializer to be possibly called on Self which is a subclass of UIView in this extension.

    It is getting even more fun when you realize that this initializer is not called directly because in loadFromNib declaration, no-argument initializer init() from NSObject is used (which underneath seems to fallback to init(frame:) with CGRect.zero passed as an argument). This is even more surprising in terms of compilation since the code below (which mimics NSObject and UIView initializers) does not compile:

    class Base : NSObject {
    }
    
    class Subclass : Base {
        init(frame: CGRect){}
        required init?(coder aDecoder: NSCoder) {}
    }
    
    class Custom : Subclass {
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
        }
    }
    
    extension NibOwnerLoadable where Self : Subclass {
        static func testing(test: Self = Self()) -> Self {
            return test
        }
    }
    

    and produces compile-time error saying:

    Cannot invoke initializer for type 'Self' with no arguments

    or if we change where Self : Subclass to where Self : Base

    Constructing an object of class type 'Self' with a metatype value must use a 'required' initializer

    which is the best description of what is happening.

    But for some reasons it compiles when using UIView ...

    Having all this in mind, I have created a pull request for increased safety when using init(frame: CGRect) to explicitly say that this is possible by implementing FrameInitializable protocol.

    So loadFromNib() with no arguments version is added to an extension of NibOwnerLoadable for UIView implementing FrameInitializable protocol and uses the argument version from NibOwnerLoadable extension for UIView. Consequently, the non-argument version does not need @discardableResult because one for sure needs to use the returned object, otherwise it is pointless calling this function.

    This is a simple and small change and it expresses more explicitly what is actually needed for UIView to safely use loadFromNib() without owner parameter.

    opened by Skoti 20
  • Converted library and Demo project to Swift 3

    Converted library and Demo project to Swift 3

    I converted the project to Swift 3 using the master branch. It was not possible to rebase swift3 branch on top of master as mentioned in #30 because of numerous conflicts.

    enhancement 
    opened by ceyhun 19
  • Renamed storyboard property to unified sceneStoryboard

    Renamed storyboard property to unified sceneStoryboard

    Current implementation for instantiating VC from storyboard is not working correctly. Self().storyboard from StoryboardSceneBased.swift is creating new instance of VC and then reading "storyboard" property. However value of read property is not from StoryboardBased.storyboard (which is static) but from UIViewController.storyboard (which is optional). I'm proposing small change (but it's breaking API change) by renaming property from 'storyboard' to 'sceneStoryboard' for avoid conflicts and make code working correctly.

    opened by ghost 16
  • loadNibContent always loads nib named after base class

    loadNibContent always loads nib named after base class

    Consider the following classes:

    class MyCustomClass: UIView, NibOwnerLoadable {
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            self.loadNibContent()
            // ... rest of init
        }
    }
    
    class MyCustomSubClass: MyCustomClass {
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            // ... rest of init
        }
    }
    

    What happens now when instantiating MyCustomSubClass is that MyCustomClass's nib file will be loaded instead of MyCustomSubClass's one. My guess is that this has something to do with the way the nib is loaded in Sources/View/NibOwnerLoadable.swift#L41. Self.nib somehow will be the the base class's nib file and not resolve to the actual class this is called on.

    opened by calmez 14
  • Swift 2.3 support

    Swift 2.3 support

    As discussed in #20, the decision we came up - is to make a new PATCH release with Swift 2.3 support. This PR contains meta changes for 2.3 support 🚀

    Also Swiftlint reported some vertical space violations. @AliSoftware could you please clarify were the spaces added on purpose?

    opened by Antondomashnev 10
  • Using app extension only API

    Using app extension only API

    Using the library not only in the "main" app but also in a Share extension, I am getting a warning

    inking against a dylib which is not safe for use in application extensions: Carthage/Build/iOS/Reusable.framework/Reusable
    

    The code probably only uses APIs that are "extension-safe", so it would be nice to check the box in the project settings to make the warning disappear.

    opened by igorkulman 9
  • Support UIViewController with xib?

    Support UIViewController with xib?

    In ios8 ViewController with xib oulets is nil if invoke init method..

    http://stackoverflow.com/questions/34589922/iboutlets-are-nil-on-ios-8-devices-but-work-fine-on-ios-9-swift

    opened by codwam 7
  • Extension for UICollectionViewCell / UITableViewCell with contentView

    Extension for UICollectionViewCell / UITableViewCell with contentView

    When working with this library when trying to put NibOwnerLoadable views into a UICollectionViewCell and UITableViewCell, I found that to get the correct view hierarchies, these extensions were required:

    public extension NibOwnerLoadable where Self: UICollectionViewCell {
        func loadNibContent() {
            let layoutAttributes: [NSLayoutConstraint.Attribute] = [.top, .leading, .bottom, .trailing]
            for case let view as UIView in Self.nib.instantiate(withOwner: self, options: nil) {
                view.translatesAutoresizingMaskIntoConstraints = false
                contentView.addSubview(view)
                NSLayoutConstraint.activate(layoutAttributes.map { attribute in
                    NSLayoutConstraint(
                        item: view, attribute: attribute,
                        relatedBy: .equal,
                        toItem: contentView, attribute: attribute,
                        multiplier: 1, constant: 0.0
                    )
                })
            }
        }
    }
    
    public extension NibOwnerLoadable where Self: UITableViewCel {
        func loadNibContent() {
            let layoutAttributes: [NSLayoutConstraint.Attribute] = [.top, .leading, .bottom, .trailing]
            for case let view as UIView in Self.nib.instantiate(withOwner: self, options: nil) {
                view.translatesAutoresizingMaskIntoConstraints = false
                contentView.addSubview(view)
                NSLayoutConstraint.activate(layoutAttributes.map { attribute in
                    NSLayoutConstraint(
                        item: view, attribute: attribute,
                        relatedBy: .equal,
                        toItem: contentView, attribute: attribute,
                        multiplier: 1, constant: 0.0
                    )
                })
            }
        }
    }
    

    Note the change above from self to contentView. This works if the root view is a plain UIView object in the .xib (but doesn't if they're UICollectionViewCell or UITableViewCell). However, I'd love to be able to do something cleaner like:

    protocol ContentViewNibLoadable {
        var contentView: UIView { get }
    }
    
    extension ContentViewNibLoadable where Self: NibOwnerLoadable { 
        func loadNibContent() {
            ...
        }
    }
    
    extension UICollectionViewCell: ContentViewNibLoadable { }
    extension UITableViewCell: ContentViewNibLoadable { }
    

    but this doesn't resolve to the correct contentView property in the cell. Any ideas on how to make this more elegant?

    opened by ndizazzo 6
  • iOS 8.0 Support

    iOS 8.0 Support

    NibOwnerLoadable uses forEach method which is introduced in iOS 9.3. I suppose this would be a problem for people supporting iOS 8.

    What does NibOwnerLoadable do anyway? Why is it adding layout constraints to the instantiated view?

    opened by gokselkoksal 6
  • Protocols need to use String(self) rather than String(Self)

    Protocols need to use String(self) rather than String(Self)

    The use of Self fails in the presence of subclassed cells because String(Self) refers to the class directly conforming to the protocol, while String(self) refers correctly to the class of the instance.

    opened by rollandjb 6
  • ## Summary

    ## Summary

    Summary

    Code to reproduce

    iOS version

    Installation method

    SDK version

    Other information

    Originally posted by @vecsdom in https://github.com/stripe/stripe-ios/issues/1962

    opened by vecsdom 0
  • Sure, go ahead!

    Sure, go ahead!

    Sure, go ahead!

    Le ven. 10 sept. 2021 à 17:35, John Arnokouros ***@***.***> a écrit :

    Hello there @AliSoftware https://github.com/AliSoftware

    I'm getting the warning below after I update to Xcode 12.5

    Using 'class' keyword for protocol inheritance is deprecated; use 'AnyObject' instead

    I can open a PR and fix it. Are you ok with it?

    — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/AliSoftware/Reusable/issues/112, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABUYGJA5N6LUU2X6P5FX33UBIQUXANCNFSM5DZSUD5Q .

    --

    -- Olivier Halligon Mobile Development Tooling & Automation

    Originally posted by @AliSoftware in https://github.com/AliSoftware/Reusable/issues/112#issuecomment-917009089

    opened by vecsdom 0
  • UITableViewHeaderFooterView - Warning with NibReusable

    UITableViewHeaderFooterView - Warning with NibReusable

    I'm trying to use the NibReusable for the HeaderFooterView for a TableView but i'm getting a warning in console:

    [TableView] Changing the background color of UITableViewHeaderFooterView is not supported. Use the background view configuration instead.

    But i'm not changing any background color in my view as you can see below. No warning if i'm just using a UIView without the dequeueReusable logic

    import Reusable
    
    class ShoppingListHeaderView: UITableViewHeaderFooterView, NibReusable {
        @IBOutlet private weak var titleLabel: UILabel!
        @IBOutlet private weak var iconImageView: UIImageView!
        @IBOutlet private weak var button: UIButton!
    
        override func awakeFromNib() {
            super.awakeFromNib()
            
            iconImageView.isHidden = true
            iconImageView.tintColor = Asset.Colors.purple1F.color
            
            titleLabel.textColor = Asset.Colors.black.color
            titleLabel.textAlignment = .left
            titleLabel.font = RawFonts.latoBold.font(size: 16)
        }
        
        func configure(text: String) {
            titleLabel.text = text
        }
    }
    
    opened by clementmorissard 0
  • Bump cocoapods-downloader from 1.5.1 to 1.6.3

    Bump cocoapods-downloader from 1.5.1 to 1.6.3

    Bumps cocoapods-downloader from 1.5.1 to 1.6.3.

    Release notes

    Sourced from cocoapods-downloader's releases.

    1.6.3

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.2

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.1

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.0

    Enhancements
    • None.
    Bug Fixes
    • Adds a check for command injections in the input for hg and git.
      orta #124
    Changelog

    Sourced from cocoapods-downloader's changelog.

    1.6.3 (2022-04-01)

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.2 (2022-03-28)

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.1 (2022-03-23)

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.0 (2022-03-22)

    Enhancements
    • None.
    Bug Fixes
    • Adds a check for command injections in the input for hg and git.
      orta #124
    Commits
    • c03e2ed Release 1.6.3
    • f75bccc Disable Bazaar tests due to macOS 12.3 not including python2
    • 52a0d54 Merge pull request #128 from CocoaPods/validate_before_dl
    • d27c983 Ensure that the git pre-processor doesn't accidentally bail also
    • 3adfe1f [CHANGELOG] Add empty Master section
    • 591167a Release 1.6.2
    • d2564c3 Merge pull request #127 from CocoaPods/validate_before_dl
    • 99fec61 Switches where we check for invalid input, to move it inside the download fun...
    • 96679f2 [CHANGELOG] Add empty Master section
    • 3a7c54b Release 1.6.1
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 0
  • Added support for UICollectionViewLayout Decoration View

    Added support for UICollectionViewLayout Decoration View

    Old Style

    import UIKit
    
    class MyCollectionViewLayout: UICollectionViewCompositionalLayout {
        required init?(coder: NSCoder) {
            super.init { (section, layoutEnvironment) -> NSCollectionLayoutSection? in
                /* code */
            }
    
            let nib: UINib = .init(nibName: String(describing: MyCollectionReusableView.self), bundle: .main)
            register(nib, forDecorationViewOfKind: String(describing: MyCollectionReusableView.self))
        }
    }
    

    New Style

    import UIKit
    import Reusable
    
    class MyCollectionViewLayout: UICollectionViewCompositionalLayout {
        required init?(coder: NSCoder) {
            super.init { (section, layoutEnvironment) -> NSCollectionLayoutSection? in
                /* code */
            }
    
            register(decorationViewType: MyCollectionReusableView.self)
        }
    }
    
    opened by pookjw 0
  • Replace String(describing:) with String(reflecting:)

    Replace String(describing:) with String(reflecting:)

    Different namespace but duplicate reuseIdentifier

    Found a not very important issue, when two internal class names are the same, thereuseIdentifier is also the same. If you use String(reflecting:), you can use the full path identifier , which faster than String(describing:). Of course, you can also override reuseIdentifier, or using two different class names, even if their namespaces are different.

    
    
    import Foundation
    
    
    public protocol Reusable: AnyObject {
    	static var reuseIdentifier: String { get }
    }
    
    extension Reusable {
    	public static var reuseIdentifier: String {
    		return String(describing: self)
    	}
    	
    	public static var reuseIdentifier2: String {
    		return String(reflecting: self)
    	}
    }
    
    class Compoment {
    	class Header { 
    		class Text: Reusable {
    	
    		}
    	}
    	class Text: Reusable {
    		
    	}
    }
    
    print(Compoment.Text.reuseIdentifier)
    
    var date = Date()
    print(Compoment.Header.Text.reuseIdentifier)
    print(-date.timeIntervalSinceNow * 1000)
    
    date = Date()
    print(Compoment.Header.Text.reuseIdentifier2)
    print(-date.timeIntervalSinceNow * 1000)
    
    
    
    //Text
    //Text
    //0.014066696166992188
    //main.Compoment.Header.Text
    //0.0069141387939453125
    
    
    opened by TBXark 0
Releases(4.1.2)
  • 4.1.2(Sep 12, 2021)

  • 4.1.1(Mar 5, 2020)

    Source code(tar.gz)
    Source code(zip)
    Reusable.framework.zip(2.99 MB)
  • 4.1.0(May 6, 2019)

  • 4.0.5(Dec 2, 2018)

  • 4.0.4(Oct 13, 2018)

  • 4.0.3(Aug 25, 2018)

  • 4.0.2(Aug 25, 2018)

  • 4.0.1(Feb 16, 2017)

  • 4.0.0(Feb 16, 2017)

    Breaking changes

    • The method static func loadFromNib(owner:) of NibOwnerLoadable has been replaced by instance method func loadNibContent().
      This is more consistent as we need an instance (owner) anyway, and also avoids possible crashes when used with UIView subclasses not implementing non-required initializers init()/init(frame:). (Method static func loadFromNib() of NibLoadable is still unchanged).
      @Skoti #40
    • The storyboard property of StoryboardBased and StoryboardSceneBased protocols has been renamed to sceneStoryboard to avoid conflicts.
      @jakubgert #33

    Fixes

    • Fixing documentation typos for CollectionHeaderView and MyXIBIndexSquaceCell.
      @Skoti
    • Fixing TableView Controller scene in Example project, to display cells above UITabBar.
      @Skoti
    Source code(tar.gz)
    Source code(zip)
    Reusable.framework.zip(536.02 KB)
  • 3.0.1(Dec 5, 2016)

  • 3.0.0(Nov 14, 2016)

    • Converted library and Demo project to Swift 3.
      ⚠️ BREAKING CHANGES ⚠️ The following methods have new signatures:
      • dequeueReusableCell(indexPath:) is now dequeueReusableCell(for:)
      • dequeueReusableCell(indexPath:cellType:) is now dequeueReusableCell(for:cellType:)
      • registerReusableCell(_:) is now register(cellType:)
      • registerReusableHeaderFooterView(_:) is now register(headerFooterViewType:)
      • registerReusableSupplementaryView(_:viewType:) is now register(supplementaryViewType:ofKind:)
      • dequeueReusableSupplementaryView(_:indexPath:viewType:) is now dequeueReusableSupplementaryView(ofKind:for:viewType:)
        @phatblat #30
        @ceyhuno #31
    Source code(tar.gz)
    Source code(zip)
    Reusable.framework.zip(537.47 KB)
  • 2.5.1(Nov 6, 2016)

  • 2.5.0(Oct 12, 2016)

    • Added the possibility for NibOwnerLoadable confirming custom views to pass an existing instance as owner (used as the File's Owner) in case one already exists. This is especially useful to implement init(coder:) to load the content of the XIB as subviews of self after initialization. See MyCustomWidget.swift for an example.
      @AliSoftware
    Source code(tar.gz)
    Source code(zip)
  • 2.4.0(Oct 12, 2016)

  • 2.3.0(Jun 2, 2016)

    • Added NibOwnerLoadable protocol for UIView set as XIB's File's Owner.
      @PoissonBallon, #16

    While the NibLoadable protocol is adapted to views loaded from XIB but that are set as the root view of the XIB, this new NibOwnerLoadable protocol is adapted to view loaded from XIB too, but that are set as the XIB's File's Owner.

    Source code(tar.gz)
    Source code(zip)
  • 2.2.1(Mar 1, 2016)

  • 2.2.0(Mar 1, 2016)

    • Added optional viewType & cellType parameters to the dequeue functions.
      @k3zi, #11

    This parameter is only needed if you can't write … as MyCell (to let Swift infer the cell type from the return type), which might be the case for example when your cell class is stored in a variable:

    let cellType: Any.Type = self.cellTypeForIndexPath(indexPath)
    // Can't do this in this case (because cellType is a variable):
    let cell = tableView.dequeueReusableCell(indexPath: indexPath) as cellType ❌ // compiler error
    // But now we can use that alternate way for such cases:
    let cell = tableView.dequeueReusableCell(indexPath: indexPath, cellType: cellType)
    

    But if you know the type at compile time, you can omit the cellType parameter and still do this, letting the return type infer it for you:

    let cell = tableView.dequeueReusableCell(indexPath: indexPath) as MyCell
    
    Source code(tar.gz)
    Source code(zip)
  • 2.1.1(Mar 1, 2016)

    • Made every method final to allow more optimizations.
      @AliSoftware
    • Banned the use of as! in the source code in favour of guard let x = y else { fatalError(…) }. This avoids force-casts (which are considered bad practice) and generate a more explicit fatal error in case the developer forgot something (typically forgot to set the reuseIdentifier in IB).
      @AliSoftware, #6
    • Fixed bundle location of nibs. By default, nib: UINib of NibLoadable protocol will now use the nib located in the bundle of the conforming class.
      @chrisamanse, #10
    • Fixed issue with subclasses of types conforming to Reusable — due to the Swift bug SR-617.
      @chrisamanse, #2
    Source code(tar.gz)
    Source code(zip)
  • 2.1.0(Feb 1, 2016)

    • Added support for direct instantiation of arbitrary UIView from a nib.
      @jakubvano, #5

    There is now a dedicated NibLoadable protocol which can be used on any arbitrary UIView (even non-"reusable" views) to load it from a XIB (via the loadFromNib() function injected via the protocol extension).

    The NibReusable protocol still exists for reusable cells but is now declared just as a combination of both the Reusable and NibLoadable protocols.

    Source code(tar.gz)
    Source code(zip)
  • 2.0.0(Feb 1, 2016)

  • 1.1.0(Feb 1, 2016)

    • Added documentation
    • Fixed generic constraints on the API working with UICollectionView's SupplementaryView
    • Updated Example project to add an UICollectionView with cells from XIB & Code + Header views
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0(Feb 1, 2016)

  • 0.1.0(Feb 1, 2016)

Owner
Olivier Halligon
iOS architect & Swift lover. OSS enthusiast.
Olivier Halligon
💾 🔜📱 Type-safe data-driven CollectionView, TableView Framework. (We can also use ASCollectionNode)

⚠️ The latest updates is this PR. It changes the difference algorithm to DifferenceKit. DataSources ?? ?? ?? Type-safe data-driven List-UI Framework.

Muukii 563 Dec 16, 2022
Easy way to integrate pagination with dummy views in CollectionView, make Instagram "Discover" within minutes.

AZCollectionView Controller Features Automatic pagination handling No more awkward empty CollectionView screen AZ CollectionVIew controller give you a

Afroz Zaheer 95 May 11, 2022
TLIndexPathTools is a small set of classes that can greatly simplify your table and collection views.

TLIndexPathTools TLIndexPathTools is a small set of classes that can greatly simplify your table and collection views. Here are some of the awesome th

SwiftKick Mobile 347 Sep 21, 2022
This component allows for the transfer of data items between collection views through drag and drop

Drag and Drop Collection Views Written for Swift 4.0, it is an implementation of Dragging and Dropping data across multiple UICollectionViews. Try it

Michael Michailidis 508 Dec 19, 2022
Modern Collection Views

The goal is to showcase different compositional layouts and how to achieve them. Feel free to use any code you can find and if you have interesting layout idea - open PR!

Filip Němeček 536 Dec 28, 2022
A SwiftUI collection view with support for custom layouts, preloading, and more.

ASCollectionView A SwiftUI implementation of UICollectionView & UITableView. Here's some of its useful features: supports preloading and onAppear/onDi

Apptek Studios 1.3k Dec 24, 2022
A custom paging behavior that peeks the previous and next items in a collection view

MSPeekCollectionViewDelegateImplementation Version 3.0.0 is here! ?? The peeking logic is now done using a custom UICollectionViewLayout which makes i

Maher Santina 353 Dec 16, 2022
A guy that helps you manage collections and placeholders in easy way.

Why? As mobile developers we all have to handle displaying collections of data. But is it always as simple as it sounds? Looks like spaghetti? It is a

AppUnite Sp. z o.o. Spk. 47 Nov 19, 2021
Custom transition between two collection view layouts

Display Switcher We designed a UI that allows users to switch between list and grid views on the fly and choose the most convenient display type. List

Yalantis 2.3k Dec 14, 2022
Lightweight custom collection view inspired by Airbnb.

ASCollectionView Lightweight custom collection view inspired by Airbnb. Screenshots Requirements ASCollectionView Version Minimum iOS Target Swift Ver

Abdullah Selek 364 Nov 24, 2022
Custom CollectionViewLayout class for CollectionView paging mode to work properly

PagingCollectionViewLayout About How to use About ⚠️ Custom class, which is inherited from UICollectionViewFlowLayout, developed for properly work Col

Vladislav 2 Jan 17, 2022
Easier way to represent the structure of UITableView.

Shoyu Shoyu is a library written in Swift to represent UITableView data structures. Shoyu means Soy Sauce in Japanese. Usage Create single section and

yukiasai 278 Apr 14, 2022
Netflix and App Store like UITableView with UICollectionView, written in pure Swift 4.2

GLTableCollectionView Branch Status master develop What it is GLTableCollectionView is a ready to use UITableViewController with a UICollectionView fo

Giulio 708 Nov 17, 2022
An iOS drop-in UITableView, UICollectionView and UIScrollView superclass category for showing a customizable floating button on top of it.

MEVFloatingButton An iOS drop-in UITableView, UICollectionView, UIScrollView superclass category for showing a customizable floating button on top of

Manuel Escrig 298 Jul 17, 2022
Automates prefetching of content in UITableView and UICollectionView

Automates preheating (prefetching) of content in UITableView and UICollectionView. Deprecated on iOS 10. This library is similar to UITableViewDataSou

Alexander Grebenyuk 633 Sep 16, 2022
A data-driven UICollectionView framework for building fast and flexible lists.

A data-driven UICollectionView framework for building fast and flexible lists. Main Features ?? Never call performBatchUpdates(_:, completion:) or rel

Instagram 12.5k Jan 1, 2023
Incremental update tool to UITableView and UICollectionView

EditDistance is one of the incremental update tool for UITableView and UICollectionView. The followings show how this library update UI. They generate

Kazuhiro Hayashi 90 Jun 9, 2022
PJFDataSource is a small library that provides a simple, clean architecture for your app to manage its data sources while providing a consistent user interface for common content states (i.e. loading, loaded, empty, and error).

PJFDataSource PJFDataSource is a small library that provides a simple, clean architecture for your app to manage its data sources while providing a co

Square 88 Jun 30, 2022
Collapse and expand UICollectionView sections with one method call.

This library provides a custom UICollectionView that allows to expand and collapse sections. Provides a simple API to manage collection view appearanc

Touchlane 172 Dec 26, 2022