Code Monkey home page Code Monkey logo

reusable's Introduction

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.

reusable's People

Contributors

alisoftware avatar antondomashnev avatar ayastrebov avatar bcylin avatar calmez avatar ceyhun avatar ceyhun-monument avatar chrisamanse avatar danshevluk avatar dependabot[bot] avatar divinedominion avatar djbe avatar florentmorin avatar greg3z avatar igorkulman avatar igrandav avatar jakubvano avatar jgert avatar johnarn avatar k3zi avatar kuyazee avatar m0rtymerr avatar mahamada-gy avatar narirou avatar neilkimmett avatar nekrich avatar pahnev avatar skoti avatar zhukn1 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

reusable's Issues

[Enhancement] Typed View Controller from Storyboard

Hi, 😀

I had an idea and I would like to present it before I attempt a PR so we can discuss it first.
I am really not fan of that line:

let colorViewController = self.storyboard?.instantiateViewController(withIdentifier: "identifier") as! ColorViewController

So having a bit of play I came up with an idea (which is pretty simple and swifty .. 😀)

extension UIStoryboard {
    func instatiate<T: UIViewController>(type: T.Type, with identifier: String) -> T {
        return instantiateViewController(withIdentifier: identifier) as! T
    }
}

So the following can be possible:

let colorViewController = storyboard?.instatiate(type: ColorViewController.self, with: "identifier")

Also must of the times I use the same name of the class as a storyboard identifier, so we can improve it like that:

extension UIStoryboard {
    func instatiate<T: UIViewController>(type: T.Type, identifier: String? = nil) -> T {
        let identifier = identifier ?? "\(type.self)"
        return instantiateViewController(withIdentifier: identifier) as! T
    }
}

and can be used like that:

let colorViewController = storyboard?.instatiate(type: ColorViewController.self)

As a result a protocol conformance is no longer required. 😀
This is just a draft of my thoughts but please let me know if you believe that Reusable can facilitate this approach.

CollectionView's dequeueReusableSupplementaryView(ofKind:for:viewType) not available

Hello :)
I'm trying to use the UICollectionView's dequeueReusableSupplementaryView(ofKind:for:viewType) function but I struggle with the viewType parameter.

From the function definition, the viewType parameter must be a UICollectionReusableView, and also a Reusable.

This first snippet compiles correctly:

let headerType = TitleHeaderView.self
      let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, for: indexPath, viewType: headerType)

But this next one doesn't compiles and gives me a Argument 'viewType' must precede argument 'for' error.

//This func is responsible for returning the correct header class according to the given indexPath
func retrieveHeaderType() -> (UICollectionReusableView & Reusable).Type { ... }

let headerType = retrieveHeaderType()
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, for: indexPath, viewType: headerType)

As I understand it, this error comes up because my headerType doesn't have the func's required type, and the compilator think I want to use the classic dequeue func.
But isn't a UICollectionReusableView & Reusable a correct type for dequeueReusableSupplementaryView(ofKind:for:viewType) ?
What type should my headerType func return in order to work?

Reusable version: 4.0.2 (imported with cocoapods)
Xcode 9.3
App project in Swift 4.1
Reusable project in Swift 3.3

Swift 4.2 support?

Thanks for good work. Then a new swift version has appeared, please for another upgrade as soon as possible?

inherited class behavior

Hi.

When this plugin registered inherited cell, it does dequeueReusableCell as its supperclass.
In this case, it will occur crash because registered class and dequeued class is different.

class BaseCell: UICollectionViewCell, Reusable {}
class MyCell: BaseCell {}

let cellType: BaseCell.Type = MyCell.self

class CollectionViewController: UICollectionViewController {
  override func viewDidLoad() {

    // it is registered as "BaseCell". But I think this cell should be registered as "MyCell".
    collectionView.registerReusableCell(cellType)
  }

  override func collectionView(collectionView: UICollectionView,
    cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

    // in this statement, the cell is "MyCell". (will be crashed)
    let cell = collectionView.dequeueReusableCell(indexPath: indexPath, cellType: cellType)

    return cell
  }

  // ...
}

If possible, I will send a pull-request. :)
Thanks.

Swift 4.2 issue

Reusable/Sources/View/NibOwnerLoadable.swift:40:28: error: 'NSLayoutAttribute' has been renamed to 'NSLayoutConstraint.Attribute'

There's an error when compiling against Xcode 10 beta 6

Xcode 11 beta 4 compiler error

Steps to reproduce

  • Using Xcode 10.2.1, create brand-new project (single view app, I named mine Test01).
  • Configure Test01 to target iOS 11.
  • Quit Xcode 10.2.1
  • Using latest Cocoapods tool (1.7.2), enable pods with Reusable 4.1.0 as the sole dependency (see Podfile.txt).
  • Open Xcode 10.2.1, and verify compiling the project workspace (Test01.workspace).
  • Quit Xcode 10.2.1
  • Start Xcode 11.0 beta 3, and verify compiling the project workspace.
  • Quit Xcode 11.0 beta 3
  • Start Xcode 11.0 beta 4, and verify compiling the project workspace

Expected behavior

  • Compile with no error

Actual behavior

Notes

  • I discovered this issue when testing Beta 4 (released July 17th) on my current project, and validated that this compiler error happens with a brand-new project as well.
  • I manually clear my DerivedData folder (delete it via bash script) every time before I startup a different version of Xcode.

Improve loadFromNib method

A view class can be instantiated from multiple nibs, even if that's not really common. I have 2 suggestions to cover this kind of situations:

  • Rename nib to defaultNib because it may have multiple nibs.
  • Rename loadFromNib() to:
static func instantiateFromNib(
    name: String? = nil, 
    bundle: NSBundle = NSBundle.mainBundle(), 
    owner: AnyObject? = nil, 
    options: [NSObject: AnyObject]? = nil) 
    -> Self?

(I also renamed load to instantiate to keep consistent with Storyboard protocols.)

I can make this improvement if you are OK with it.

Great library, thanks for sharing. :)

StoryboardSceneBased crashing

I encountered this problem in my app and realized it's also there in the example app.

It's easy to reproduce by launching the example app in the simulator and selecting "Details" on a table view cell.

The error reads fatal error: The viewController 'InfoDetailViewController' of 'nil' is not of class 'InfoDetailViewController'

Support for new API introduced on iOS 13

Hi,

First of all thank you very much for such a useful pod! I used to do this manually but now having it on a pod is definitely more reliable :D

One question though, is there any plans to support the new API instantiateViewController(identifier:creator:)?

TIA

Conformance of 'UICollectionViewCell' to protocol 'Reusable' was already stated in the type's module 'UIKit'

I've added extensions for UITableViewCell, UITableViewHeaderFooterView, UICollectionViewCell and UICollectionReusableView in the protocol file:

extension UITableViewCell: Reusable {}
extension UITableViewHeaderFooterView: Reusable {}
extension UICollectionViewCell: Reusable {}
extension UICollectionReusableView: Reusable {}

So that I don't need to make all my custom cells/views conform to Reusable, and can assume all custom cells/views will be Reusable. This works, however, the compiler is complaining about the UICollectionViewCell with "Conformance of 'UICollectionViewCell' to protocol 'Reusable' was already stated in the type's module 'UIKit'".

I can't see how the collection view cell is trying to use Reusable in UIKit. As well, I tried changing the name of the protocol to ReusableView and it still complained. I also tried adding the name of my module before Reusable, so it became: extension UICollectionViewCell: MyModule.Reusable {}.

Any ideas how to make the compiler be quiet about this?

Thanks

Undefined symbols when compiling for unit tests

I've setup unit tests for my project and want to add View/ViewController instantiation to the tests because the project is modular. When compiling this error message appears when for any methods that came from Reusable.

Undefined symbols for architecture x86_64:
  "static (extension in Reusable):Reusable.StoryboardSceneBased< where A: __C.UIViewController>.instantiate() -> A", referenced from:
      implicit closure #1 () throws -> FooProject.HomeViewController? in FooProjectTests.InstantiationTests.testInstantiations() -> () in InstantiationTests.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

I'm not sure why this is happening because the other frameworks can be accessed easily. This is a sample code that generates the error

@testable import FooProject
import Nimble
import XCTest

class InstantiationTests: XCTestCase {
    func testInstantiations() {
        expect(HomeViewController.instantiate()).notTo(raiseException())
    }
}

Possible Faqs

Is the project enabled for testability
Yes

Do you have other tests besides this?
Yes

What do you mean by the project being modular?
The project has multiple Targets and Configurations. ex

Configurations

  • Release
  • Release Debug
  • Staging
  • Staging Debug

Targets

  • FooProject
  • FooProjectV2

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?

Incompatible Swift version

I'm getting this error running a Carthage update:

Skipped installing Reusable.framework binary due to the error

Incompatible Swift version - framework was built with swiftlang-800.0.63 clang-800.0.42.1 and the local version is swiftlang-802.0.53 clang-802.0.42.

Any idea?

IBOutlet is nil, when NibReusable is prototyped in storyboard instead of registered by code

I followed the readme and created MyCell.swift, MyCell.xib, changed prototype class and reusable identifier in storyboard to MyCell. I think I don't need to call register in controller any more, am I right? I tried to register in viewDidLoad of my controller. It worked, but, the cells don't trigger segues any more. I assume it's been overwritten by the register. What did I do wrong?

Problem while compiling with Carthage

Steps to reproduce

in Cartfile add this > github "AliSoftware/Reusable"
$ carthage update

Carthage console:

*** Downloading Reusable.framework binary at "4.0.5"
***  Skipped installing Reusable.framework binary due to the error:
	"Incompatible Swift version - framework was built with 4.2 (swiftlang-1000.11.37.1 clang-1000.11.45.1) and the local version is 4.2.1 (swiftlang-1000.11.42 clang-1000.11.45.1)."

check it plz

NibLoadable subviews are nil in initializer

Hi. I made a NibLoadable view and added subviews.
However, those views are nil inside required init?(coder:) so I can't setup my views. (e.g. I want to set corner radius to a subview)

Where can I put additional init logic for NibLoadable?

NibReusable should have bundle as an optional parameter

In your example of Customize reuseIdentifier, nib, etc for non-conventional uses

static var nib: UINib { return UINib(nibName: "VeryCustomUI", bundle: nil) } // Use VeryCustomUI.xib

The bundle should be a parameter and could look like this instead

static var nib: UINib { return self.nib(bundle: nil) } // Use VeryCustomUI.xib

static func nib(bundle: Bundle?) -> UINib { return UINib(nibName: "VeryCustomUI", bundle: bundle) } // Use VeryCustomUI.xib

The reason behind this is that, if an app is using custom localizations, the way to get the localized version of a Nib is by doing this:

let path = Bundle.main.path(forResource: "SomeResource", ofType: "lproj")!
let bundle = Bundle(path: path)!
let nib = UINib(nibName: "VeryCustomUI", bundle: bundle)

// example VC
class SampleViewController: UIViewController {
    init(bundle: Bundle?) {
        super.init(nibName: "SampleViewController", bundle: bundle)
    }
}

// example UITableViewCell
let name: String = "SampleTableViewCell"
tableView.register(
    UINib(nibName: name, bundle: sampleBundle),
    forCellReuseIdentifier: name)

It would be great if you could do this.

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.

Migrate from `as!` to `guard let else fatalError(…)` construct

I tend to avoid the use of force-unwrapping ! and force-casting as!.

I think it's preferable to use guard let x = y as? Y else { fatalError("…") } and use an explicit and helpful error in the fatalError(…) call rather than let x = y as! Y

There are not many force-casts in the codebase so that should be pretty quick to do.

tvOS scheme not working

Via Carthage:
Dependency "Reusable" has no shared framework schemes for any of the platforms: tvOS

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?

How do you inject dependencies into your view controllers

Hey Oliver, I ran into an issue designing my API and wanted some advice. With many of my view controllers, I require dependencies for the controller to function properly. If I use the instantiate function vended by your library, I create another function that will fill out a bunch of implicitly wrapped optionals, like so:

class MyVC: UIViewController {
    private var userService: UserService!

    static func instantiate(with userService: UserService) -> Self {
         let vc = Self.instantiate() // from Reusable
         vc.userService = userService
         return vc
    }
}

have you come up with a better pattern? Or are you doing something similar

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.

Consider adopting the responsibility of registering before dequeueing

Hi,

As the titles says. I've been using this approach for some time:

public extension UITableView {
    /// Registers and dequeues a `Reusable` `UITableViewCell`.
    ///
    /// - Returns: A reusable cell.
    final func reusableCell<T: UITableViewCell>() -> T where T : Reusable {
        guard let cell = self.dequeueReusableCell(withIdentifier: T.reuseIdentifier) as? T else {
            self.register(T.self, forCellReuseIdentifier: T.reuseIdentifier)
            return self.dequeueReusableCell(withIdentifier: T.reuseIdentifier) as! T
        }
        return cell
    }
    
}

Then:

let cell = tableView.reusableCell() as TableViewCell

No need to remind myself to register beforehand. I know the signatures don't match. It's just to get an idea.

Is there some edge case I'm not seeing where this might not be possible/advisable?

Thanks

getting error on using cellType(for indexPath:)

on trying below getting erros

`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
}`

<2020-03-08 20:37:13.830457+0500 ReusableDemo iOS[18026:477363] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Attempted to dequeue multiple cells for the same index path, which is not allowed. If you really need to dequeue more cells than the table view is requesting, use the -dequeueReusableCellWithIdentifier: method (without an index path). Cell identifier: MyXIBTextCell, index path: <NSIndexPath: 0x8edbb09a3c0860b8> {length = 2, path = 1 - 0}>

Extension for non reusable views

I have been pointed to this lib after having created almost the same thing for one of our projects. I have done a couple of things differently, so I thought sharing them here might lead to a productive discussion:

public protocol Loadable {
    static var nib: UINib { get }
}

public protocol Reusable {
    static var reuseIdentifier: String { get }
}

public protocol LoadableCell: Reusable, Loadable {}

Having no direct relation between Loadable and Reusable allows for usage with UIViews designed in xib:

public extension Loadable where Self: UIView {
    static func loadFromNib() -> Self {
        return nib.instantiateWithOwner(nil, options: nil).first as! Self
    }
}

This is of course possible with NibReusable as well, but for simple loading of UIView from UINib name Loadable is a better fit.

Thoughts?

Pass in Type as Parameters

I ran across an issue where Swift wasn't allowing me to supply a type that was produced from a function

func classTypeForTable(tableView: UITableView) -> ReusableCell.Type {
     return MyCell.Type
}

// In cellForRow //
let cellClass = classTypeForTable(tableView)
let cell = tableView.dequeueReusableCell(indexPath) as cellClass

^^^^^^ This last line fails because "cellClass is not a type"

I was able to get around this by changing the dequeue function and adding another:

func dequeueReusableCell<T: UITableViewCell where T: Reusable>(indexPath indexPath: NSIndexPath) -> T {
        return self.dequeueReusableCell(indexPath, cellType: T.self)
    }

func dequeueReusableCell<T: UITableViewCell where T: Reusable>(indexPath: NSIndexPath, cellType: T.Type) -> T {
       return self.dequeueReusableCellWithIdentifier(T.reuseIdentifier, forIndexPath: indexPath) as! T
    }

This would seem like a function you might want to add for advanced users...

Support for NSTableView/NSCollectionView?

Are there any intentions to extend Reusable to macOS, specifically NSTableView/NSCollectionView? The library is obviously focused on iOS/UIKit but I thought I'd still ask :). Thanks!

MKMapView support

This library is obviously concentrated on UITableView and UICollectionView support but MKMapView has a lot of the same concepts: registering annotations and dequeuing them.

I currently use a simple extension for that

extension MKMapView {
    final func register<T: MKAnnotationView>(annotationType: T.Type)
      where T: Reusable {
        self.register(annotationType.self, forAnnotationViewWithReuseIdentifier: annotationType.reuseIdentifier)
    }

    final func dequeueReusableAnnotation<T: MKAnnotationView>(annotationType: T.Type = T.self) -> T
    where T: Reusable {
        guard let view = self.dequeueReusableAnnotationView(withIdentifier: annotationType.reuseIdentifier) as? T else {
          fatalError(
            "Failed to dequeue a annotation view with identifier \(annotationType.reuseIdentifier) matching type \(annotationType.self). "
              + "Check that the reuseIdentifier is set properly in your XIB/Storyboard "
              + "and that you registered the annotation view beforehand"
          )
        }
        return view
    }
}

I wonder if you be willing to add MKMapView support. I can provide this extension as a PR together with some documentation and maybe even a sample.

Swift 5 Support?

Not that it doesn't work as is, but the Xcode warning ('Conversion to Swift 5 is available') that appears when using Reusable with Cocoapods is rather annoying.

Support Carthage

This project now supports CocoaPods and Swift Version Manager.
Could you add Carthage installation support to this project?

Thanks.

Working with multiple scenes in one Storyboard

Hi!

I'm stuck with instantiating controllers from Storyboards. In my project, I have several storyboards with multiple scenes(controllers) in each storyboard.

Intro.storyboard (IntroViewController, SpalshViewController)
Main.storyboard (ViewController1, ViewController2, etc)

In my ViewController I use protocol StoryboardSceneBased, but obviously, it is not working.
Does Reusable ability to instantiate controllers like I need?

Reusable don't compile

Hi,

I have updated my Xcode and sine the Reusable module does't compile. The error message is :

Module file was created by an older version of the compiler; rebuild 'Reusable' and try again.

The Xcode version is : 7.2.1

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.