GithubHelp home page GithubHelp logo

alisoftware / reusable Goto Github PK

View Code? Open in Web Editor NEW
3.0K 52.0 235.0 2.71 MB

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

License: MIT License

Swift 86.55% Ruby 13.45%

reusable's Issues

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

tvOS scheme not working

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

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?

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'

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

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

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.

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

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.

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?

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. :)

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.

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.

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

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

[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.

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.

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?

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?

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.

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.

Support Carthage

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

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

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

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

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?

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...

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?

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

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.

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?

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!

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.