Last active
July 4, 2022 13:32
-
-
Save bojan/b376f94578c27f694cb55116c2ed2225 to your computer and use it in GitHub Desktop.
IB auto-magical loading
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE | |
Version 2, December 2004 | |
Copyright (C) 2004 Sam Hocevar <[email protected]> | |
Everyone is permitted to copy and distribute verbatim or modified | |
copies of this license document, and changing it is allowed as long | |
as the name is changed. | |
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE | |
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | |
0. You just DO WHAT THE FUCK YOU WANT TO. | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import UIKit | |
// MARK: - Protocol declaration | |
/// A utility protocol for auto-magically loading of nib-backed views. | |
/// In order to use the protocol, you need to conform your `UIView` subclass to it. | |
/// Also, make sure to set the class name in the nib (.xib). | |
/// | |
/// - Important: | |
/// - When the view is loaded in **code**, then you have to set the class name for the **View** property. | |
/// - When the view is loaded in **a storyboard**, you have to set the class name in the **File's owner** property. | |
/// | |
/// If the view is intended to be used in a storyboard, you'll need to create an intermediate view which will contain the nib content. | |
/// The following sample should be used only as a basis. | |
/// ``` | |
/// class FooView: UIView, NibBackedView { | |
/// private var contentView: UIView? | |
/// | |
/// required init?(coder aDecoder: NSCoder) { | |
/// super.init(coder: aDecoder) | |
/// configure() | |
/// } | |
/// | |
/// override init(frame: CGRect) { | |
/// super.init(frame: frame) | |
/// configure() | |
/// } | |
/// | |
/// private func configure() { | |
/// let view = loadFromNib(useInStoryboard: true) | |
/// view.frame = self.bounds | |
/// addSubview(view) | |
/// contentView = view | |
/// } | |
/// } | |
/// ``` | |
public protocol NibBackedView: Reusable { | |
/// Returns the nib name, equal to the class name. | |
static var nibName: String { get } | |
/// Returns the nib from the current bundle. | |
static var nib: UINib { get } | |
/// Returns the size of the view as set in the nib. | |
/// - Note: | |
/// When loading from nib, the size of the view is the one set in the actual nib. | |
static var estimatedSize: CGSize { get } | |
/// A shorthand method for loading the view from its associated nib for the current instance. | |
/// - Parameter useInStoryboard: When used in a storyboard, the nib needs the **File's owner** set. | |
/// - Important: | |
/// - Intended for loading views both in code and storyboards. | |
func loadFromNib(useInStoryboard: Bool) -> UIView | |
/// A static method for loading the view from its associated nib. | |
/// - Important: | |
/// - Intended for loading views in code. | |
static func loadFromNib() -> Self | |
} | |
// MARK: - Default protocol implementation | |
extension NibBackedView { | |
public static var nibName: String { | |
reuseIdentifier | |
} | |
public static var nib: UINib { | |
UINib(nibName: nibName, | |
bundle: Bundle(for: self)) | |
} | |
public func loadFromNib(useInStoryboard: Bool = false) -> UIView { | |
// The owner needs to be passed only when we use the view from storyboards. | |
Self.loadFromNib(owner: useInStoryboard ? self : nil) | |
} | |
public static func loadFromNib() -> Self { | |
// The owner should be nil for views loaded in code. | |
loadFromNib(owner: nil) | |
} | |
/// Load a view from a nib safely or throw a fatal error in debug builds. | |
/// - Parameters: | |
/// - owner: An owner object of the loaded view. | |
private static func loadFromNib<T>(owner: Any?) -> T { | |
// When a view is loaded from a nib and used in a storyboard, we need to pass the owner to the nib instantiating method. | |
// If an instance cannot be loaded, it is usually due to configuration. | |
guard let view = nib.instantiate(withOwner: owner, options: nil).first as? T else { | |
let errorMessage = (owner != nil) | |
? "Check if the File's owner has been to set to \(nibName) in the nib itself." | |
: "Check if the class of the root view has been set to \(nibName) in the nib itself." | |
fatalError("Could not instantiate a view from \(nibName)! \(errorMessage)") | |
} | |
// Return the loaded view. | |
return view | |
} | |
public static var estimatedSize: CGSize { | |
loadFromNib().bounds.size | |
} | |
} | |
// MARK: - Table view cells | |
extension NibBackedView where Self: UITableViewCell { | |
// MARK: - Table cell registration | |
/// Registers this cell for use in the passed table view. | |
/// - Parameter tableView: The table view which should register the cell. | |
public static func register(for tableView: UITableView) { | |
tableView.register(nib, | |
forCellReuseIdentifier: reuseIdentifier) | |
} | |
} | |
// MARK: - Table view header and footer views | |
extension NibBackedView where Self: UITableViewHeaderFooterView { | |
// MARK: - Table cell registration | |
/// Registers this view for use in the passed table view as a header or footer. | |
/// - Parameter tableView: The table view which should register the view. | |
public static func registerHeaderFooter(for tableView: UITableView) { | |
tableView.register(nib, | |
forHeaderFooterViewReuseIdentifier: reuseIdentifier) | |
} | |
} | |
// MARK: - Collection view cells | |
extension NibBackedView where Self: UITableViewHeaderFooterView { | |
// MARK: - Collection cell registration | |
/// Registers this cell for use in the passed collection view. | |
/// - Parameter collectionView: The collection view which should register the cell. | |
public static func register(for collectionView: UICollectionView) { | |
collectionView.register(nib, | |
forCellWithReuseIdentifier: reuseIdentifier) | |
} | |
} | |
// MARK: - Collection view reusable views | |
extension NibBackedView where Self: UICollectionReusableView { | |
// MARK: - Reusable view registration | |
/// Registers this view for use in the passed table view as a header. | |
/// - Parameter collectionView: The collection view which should register the view. | |
public static func registerHeader(for collectionView: UICollectionView) { | |
registerSupplementaryView(for: collectionView, | |
of: UICollectionView.elementKindSectionHeader) | |
} | |
/// Registers this view for use in the passed table view as a header. | |
/// - Parameter collectionView: The collection view which should register the view. | |
public static func registerFooter(for collectionView: UICollectionView) { | |
registerSupplementaryView(for: collectionView, | |
of: UICollectionView.elementKindSectionFooter) | |
} | |
/// Registers this view for use in the passed table view as a header or a footer. | |
/// - Parameters: | |
/// - collectionView: The collection view which should register the view. | |
/// - kind: The kind of supplementary view to register. | |
private static func registerSupplementaryView(for collectionView: UICollectionView, | |
of kind: String) { | |
collectionView.register(nib, | |
forSupplementaryViewOfKind: kind, | |
withReuseIdentifier: reuseIdentifier) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import UIKit | |
// MARK: - Protocol declaration | |
/// Describes any view that uses the reusability mechanism. | |
public protocol ReusableView: UIView, StoryboardIdentifiable { | |
/// Returns the reuse identifier, defaults to the class name. | |
static var reuseIdentifier: String { get } | |
} | |
// MARK: - Default protocol implementation | |
extension ReusableView { | |
public static var reuseIdentifier: String { | |
identifier | |
} | |
} | |
// MARK: - Default Conformance | |
extension UITableViewCell: ReusableView {} | |
extension UITableViewHeaderFooterView: ReusableView {} | |
extension UICollectionReusableView: ReusableView {} | |
// MARK: - Table view cells | |
extension ReusableView where Self: UITableViewCell { | |
// MARK: - Dequeuing | |
/// Returns a reusable cell of this class. Replaces the `UITableView.dequeueReusableCell(withIdentifier:for:)` method. | |
/// - Parameters: | |
/// - tableView: The table view which should dequeue the cell. | |
/// - indexPath: The index path specifying the location of the cell. | |
/// - Note: | |
/// Make sure the cell is registered for use in the passed table, use the `register(for:)` method. | |
public static func dequeue(from tableView: UITableView, | |
at indexPath: IndexPath) -> Self { | |
guard let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, | |
for: indexPath) as? Self | |
else { | |
fatalError("Could not dequeue a cell for \(reuseIdentifier)! For nib-backed cells, check if the class name has been set in the nib itself.") | |
} | |
return cell | |
} | |
} | |
// MARK: - Table view header and footer views | |
extension ReusableView where Self: UITableViewHeaderFooterView { | |
// MARK: - Dequeuing | |
/// Returns a reusable header or footer view of this class. Replaces the `UITableView.dequeueReusableHeaderFooterView(withIdentifier:)` method. | |
/// - Parameter tableView: The table view which should dequeue the view. | |
/// - Note: | |
/// Make sure the cell is registered for use in the passed table, use the `registerHeaderFooter(for:)` method. | |
public static func dequeueHeaderFooterView(from tableView: UITableView) -> Self { | |
guard let cell = tableView.dequeueReusableHeaderFooterView(withIdentifier: reuseIdentifier) as? Self else { | |
fatalError("Could not dequeue a header/footer view from \(reuseIdentifier)! For nib-backed cells, check if the class name has been set in the nib itself.") | |
} | |
return cell | |
} | |
} | |
// MARK: - Collection view cells | |
extension ReusableView where Self: UICollectionViewCell { | |
// MARK: - Dequeuing | |
/// Returns a reusable cell of this class. Replaces the `UICollectionView.dequeueReusableCell(withIdentifier:for:)` method. | |
/// - Parameters: | |
/// - collectionView: The collection view which should dequeue the cell. | |
/// - indexPath: The index path specifying the location of the cell. | |
public static func dequeue(from collectionView: UICollectionView, | |
at indexPath: IndexPath) -> Self { | |
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, | |
for: indexPath) as? Self else { | |
fatalError("Could not dequeue a cell from \(reuseIdentifier)! For nib-backed cells, check if the class name has been set in the nib itself.") | |
} | |
return cell | |
} | |
} | |
// MARK: - Collection view reusable views | |
extension ReusableView where Self: UICollectionReusableView { | |
// MARK: - Dequeuing | |
/// Returns a reusable header view of this class. Replaces the `UITableView.dequeueReusableSupplementaryView(ofKind:withReuseIdentifier:for:)` method. | |
/// - Parameters: | |
/// - collectionView: The collection view which should dequeue the view. | |
/// - indexPath: The index path specifying the location of the view. | |
public static func dequeueHeader(from collectionView: UICollectionView, | |
at indexPath: IndexPath) -> Self { | |
dequeueSupplementaryView(from: collectionView, | |
at: indexPath, | |
of: UICollectionView.elementKindSectionHeader) | |
} | |
/// Returns a reusable footer view of this class. Replaces the `UITableView.dequeueReusableSupplementaryView(ofKind:withReuseIdentifier:for:)` method. | |
/// - Parameters: | |
/// - collectionView: The collection view which should dequeue the view. | |
/// - indexPath: The index path specifying the location of the view. | |
public static func dequeueFooter(from collectionView: UICollectionView, at indexPath: IndexPath) -> Self { | |
dequeueSupplementaryView(from: collectionView, | |
at: indexPath, | |
of: UICollectionView.elementKindSectionFooter) | |
} | |
/// Returns a reusable supplementary view of this class. Replaces the `UITableView.dequeueReusableSupplementaryView(ofKind:withReuseIdentifier:for:)` method. | |
/// - Parameters: | |
/// - collectionView: The collection view which should dequeue the view. | |
/// - indexPath: The index path specifying the location of the view. | |
/// - kind: The kind of supplementary view to dequeue. | |
private static func dequeueSupplementaryView(from collectionView: UICollectionView, | |
at indexPath: IndexPath, | |
of kind: String) -> Self { | |
guard let cell = collectionView.dequeueReusableSupplementaryView(ofKind: kind, | |
withReuseIdentifier: reuseIdentifier, | |
for: indexPath) as? Self else { | |
fatalError("Could not dequeue a supplementary view from \(reuseIdentifier)! For nib-backed cells, check if the class name has been set in the nib itself.") | |
} | |
return cell | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import UIKit | |
// MARK: - Protocol declaration | |
/// A protocol describing types that have a storyboard identifier. | |
public protocol StoryboardIdentifiable { | |
/// Returns a storyboard identifier used by view controller and table/collection cell subclasses. | |
/// | |
/// By convention, the identifier (set in Interface Builder) and the class name **should be identical**. | |
/// | |
///- Important: For nib-backed cells, the filename should also be equal to this identifier. | |
static var identifier: String { get } | |
} | |
// MARK: - Default Implementation | |
extension StoryboardIdentifiable { | |
public static var identifier: String { | |
String(describing: self) | |
} | |
} | |
// MARK: - Default Conformance | |
extension UIViewController: StoryboardIdentifiable {} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment