Created
February 23, 2021 09:33
-
-
Save vitorventurin/0b706f0e9b5a686700c95380dfd1e1d7 to your computer and use it in GitHub Desktop.
My favorite extensions from Swift Language / iOS Cocoa UIKit
This file contains hidden or 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
// The version of Swift is 4.0. | |
// Get a class name | |
public protocol ClassNameProtocol { | |
static var className: String { get } | |
var className: String { get } | |
} | |
public extension ClassNameProtocol { | |
public static var className: String { | |
return String(describing: self) | |
} | |
public var className: String { | |
return type(of: self).className | |
} | |
} | |
extension NSObject: ClassNameProtocol {} | |
UIView.className //=> "UIView" | |
UILabel().className //=> "UILabel" | |
Register / Dequeue XIB | |
UITableView | |
public extension UITableView { | |
public func register<T: UITableViewCell>(cellType: T.Type, bundle: Bundle? = nil) { | |
let className = cellType.className | |
let nib = UINib(nibName: className, bundle: bundle) | |
register(nib, forCellReuseIdentifier: className) | |
} | |
public func register<T: UITableViewCell>(cellTypes: [T.Type], bundle: Bundle? = nil) { | |
cellTypes.forEach { register(cellType: $0, bundle: bundle) } | |
} | |
public func dequeueReusableCell<T: UITableViewCell>(with type: T.Type, for indexPath: IndexPath) -> T { | |
return self.dequeueReusableCell(withIdentifier: type.className, for: indexPath) as! T | |
} | |
} | |
tableView.register(cellType: MyCell.self) | |
tableView.register(cellTypes: [MyCell1.self, MyCell2.self]) | |
let cell = tableView.dequeueReusableCell(with: MyCell.self, for: indexPath) | |
UICollectionView | |
public extension UICollectionView { | |
public func register<T: UICollectionViewCell>(cellType: T.Type, bundle: Bundle? = nil) { | |
let className = cellType.className | |
let nib = UINib(nibName: className, bundle: bundle) | |
register(nib, forCellWithReuseIdentifier: className) | |
} | |
public func register<T: UICollectionViewCell>(cellTypes: [T.Type], bundle: Bundle? = nil) { | |
cellTypes.forEach { register(cellType: $0, bundle: bundle) } | |
} | |
public func register<T: UICollectionReusableView>(reusableViewType: T.Type, | |
ofKind kind: String = UICollectionElementKindSectionHeader, | |
bundle: Bundle? = nil) { | |
let className = reusableViewType.className | |
let nib = UINib(nibName: className, bundle: bundle) | |
register(nib, forSupplementaryViewOfKind: kind, withReuseIdentifier: className) | |
} | |
public func register<T: UICollectionReusableView>(reusableViewTypes: [T.Type], | |
ofKind kind: String = UICollectionElementKindSectionHeader, | |
bundle: Bundle? = nil) { | |
reusableViewTypes.forEach { register(reusableViewType: $0, ofKind: kind, bundle: bundle) } | |
} | |
public func dequeueReusableCell<T: UICollectionViewCell>(with type: T.Type, | |
for indexPath: IndexPath) -> T { | |
return dequeueReusableCell(withReuseIdentifier: type.className, for: indexPath) as! T | |
} | |
public func dequeueReusableView<T: UICollectionReusableView>(with type: T.Type, | |
for indexPath: IndexPath, | |
ofKind kind: String = UICollectionElementKindSectionHeader) -> T { | |
return dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: type.className, for: indexPath) as! T | |
} | |
} | |
collectionView.register(cellType: MyCell.self) | |
collectionView.register(cellTypes: [MyCell1.self, MyCell2.self]) | |
let cell = collectionView.dequeueReusableCell(with: MyCell.self, for: indexPath) | |
collectionView.register(reusableViewType: MyReusableView.self) | |
collectionView.register(reusableViewTypes: [MyReusableView1.self, MyReusableView2.self]) | |
let view = collectionView.dequeueReusableView(with: MyReusableView.self, for: indexPath) | |
Instantiate a viewController in a storyboard | |
public enum StoryboardInstantiateType { | |
case initial | |
case identifier(String) | |
} | |
public protocol StoryboardInstantiatable { | |
static var storyboardName: String { get } | |
static var storyboardBundle: Bundle { get } | |
static var instantiateType: StoryboardInstantiateType { get } | |
} | |
public extension StoryboardInstantiatable where Self: NSObject { | |
public static var storyboardName: String { | |
return className | |
} | |
public static var storyboardBundle: Bundle { | |
return Bundle(for: self) | |
} | |
private static var storyboard: UIStoryboard { | |
return UIStoryboard(name: storyboardName, bundle: storyboardBundle) | |
} | |
public static var instantiateType: StoryboardInstantiateType { | |
return .identifier(className) | |
} | |
} | |
public extension StoryboardInstantiatable where Self: UIViewController { | |
public static func instantiate() -> Self { | |
switch instantiateType { | |
case .initial: | |
return storyboard.instantiateInitialViewController() as! Self | |
case .identifier(let identifier): | |
return storyboard.instantiateViewController(withIdentifier: identifier) as! Self | |
} | |
} | |
} | |
// Class name and Storyboard name, same as Storyboard ID | |
final class ViewController: UIViewController, StoryboardInstantiatable { | |
} | |
ViewController.instantiate() | |
// Initial view controller & class name and Storyboard name are the same | |
final class InitialViewController: UIViewController, StoryboardInstantiatable { | |
static var instantiateType: StoryboardInstantiateType { | |
return .initial | |
} | |
} | |
InitialViewController.instantiate() | |
Instantiate a view in XIB | |
public protocol NibInstantiatable { | |
static var nibName: String { get } | |
static var nibBundle: Bundle { get } | |
static var nibOwner: Any? { get } | |
static var nibOptions: [AnyHashable: Any]? { get } | |
static var instantiateIndex: Int { get } | |
} | |
public extension NibInstantiatable where Self: NSObject { | |
public static var nibName: String { return className } | |
public static var nibBundle: Bundle { return Bundle(for: self) } | |
public static var nibOwner: Any? { return self } | |
public static var nibOptions: [AnyHashable: Any]? { return nil } | |
public static var instantiateIndex: Int { return 0 } | |
} | |
public extension NibInstantiatable where Self: UIView { | |
public static func instantiate() -> Self { | |
let nib = UINib(nibName: nibName, bundle: nibBundle) | |
return nib.instantiate(withOwner: nibOwner, options: nibOptions)[instantiateIndex] as! Self | |
} | |
} | |
// XIB name and class name are the same & 0th View | |
final class View: UIView, NibInstantiatable { | |
} | |
View.instantiate() | |
// XIB name and class name are different & & 2nd View | |
final class View2: UIView, NibInstantiatable { | |
static var nibName: String { return "Foo" } // Foo.xib | |
static var instantiateIndex: Int { return 2 } | |
} | |
View2.instantiate() | |
Put a custom view created by Xib in Interface Builder | |
public protocol EmbeddedNibInstantiatable { | |
associatedtype Embedded: NibInstantiatable | |
} | |
public extension EmbeddedNibInstantiatable where Self: UIView, Embedded: UIView { | |
public var embedded: Embedded { return subviews[0] as! Embedded } | |
public func configureEmbededView() { | |
let view = Embedded.instantiate() | |
insertSubview(view, at: 0) | |
view.fillSuperview() // described later | |
} | |
} | |
final class EmbeddedView: UIView, NibInstantiatable { | |
} | |
@IBDesignable | |
final class IBEmbeddedView: UIView, EmbeddedNibInstantiatable { | |
typealias Embedded = EmbeddedView | |
#if TARGET_INTERFACE_BUILDER | |
override func prepareForInterfaceBuilder() { | |
super.prepareForInterfaceBuilder() | |
configureEmbededView() | |
} | |
#endif | |
override func awakeFromNib() { | |
super.awakeFromNib() | |
configureEmbededView() | |
} | |
} | |
EmbeddedView.xib | |
IBEmbeddedView | |
Make a view the same size as the superview | |
public extension UIView { | |
public func fillSuperview() { | |
guard let superview = self.superview else { return } | |
translatesAutoresizingMaskIntoConstraints = superview.translatesAutoresizingMaskIntoConstraints | |
if translatesAutoresizingMaskIntoConstraints { | |
autoresizingMask = [.flexibleWidth, .flexibleHeight] | |
frame = superview.bounds | |
} else { | |
topAnchor.constraint(equalTo: superview.topAnchor).isActive = true | |
bottomAnchor.constraint(equalTo: superview.bottomAnchor).isActive = true | |
leftAnchor.constraint(equalTo: superview.leftAnchor).isActive = true | |
rightAnchor.constraint(equalTo: superview.rightAnchor).isActive = true | |
} | |
} | |
} | |
superView.addSubview(view) | |
view.fillSuperview() | |
Get the top most UIViewController / UINavigationController | |
public extension UIApplication { | |
public var topViewController: UIViewController? { | |
guard var topViewController = UIApplication.shared.keyWindow?.rootViewController else { return nil } | |
while let presentedViewController = topViewController.presentedViewController { | |
topViewController = presentedViewController | |
} | |
return topViewController | |
} | |
public var topNavigationController: UINavigationController? { | |
return topViewController as? UINavigationController | |
} | |
} | |
UIApplication.shared.topViewController | |
Get a view controller which has the view | |
public extension UIView { | |
public var viewController: UIViewController? { | |
var parent: UIResponder? = self | |
while parent != nil { | |
parent = parent?.next | |
if let viewController = parent as? UIViewController { | |
return viewController | |
} | |
} | |
return nil | |
} | |
} | |
view.viewController | |
Create a UIColor by hexadecimal | |
public extension UIColor { | |
public convenience init(hex: Int, alpha: CGFloat = 1.0) { | |
let r = CGFloat((hex & 0xFF0000) >> 16) / 255.0 | |
let g = CGFloat((hex & 0x00FF00) >> 8) / 255.0 | |
let b = CGFloat(hex & 0x0000FF) / 255.0 | |
self.init(red: r, green: g, blue: b, alpha: alpha) | |
} | |
} | |
let color = UIColor(hex: 0xAABBCC) | |
Remove elements by the instances in an array | |
public extension Array where Element: Equatable { | |
@discardableResult | |
public mutating func remove(element: Element) -> Index? { | |
guard let index = index(of: element) else { return nil } | |
remove(at: index) | |
return index | |
} | |
@discardableResult | |
public mutating func remove(elements: [Element]) -> [Index] { | |
return elements.flatMap { remove(element: $0) } | |
} | |
} | |
let array = ["foo", "bar"] | |
array.remove(element: "foo") | |
array //=> ["bar"] | |
Remove the same value from the array | |
public extension Array where Element: Hashable { | |
public mutating func unify() { | |
self = unified() | |
} | |
} | |
public extension Collection where Element: Hashable { | |
public func unified() -> [Element] { | |
return Array(Set(self)) | |
} | |
} | |
let array = [1, 2, 3, 3, 2, 1, 4] | |
array.unify() // [1, 2, 3, 4] | |
Get an element in safety. | |
public extension Collection { | |
public subscript(safe index: Index) -> Element? { | |
return startIndex <= index && index < endIndex ? self[index] : nil | |
} | |
} | |
let array = [0, 1, 2] | |
if let item = array[safe: 5] { | |
print("unreachable") | |
} | |
Get substrings with various ranges | |
public extension String { | |
subscript (bounds: CountableClosedRange<Int>) -> String { | |
let start = index(startIndex, offsetBy: bounds.lowerBound) | |
let end = index(startIndex, offsetBy: bounds.upperBound) | |
return String(self[start...end]) | |
} | |
subscript (bounds: CountableRange<Int>) -> String { | |
let start = index(startIndex, offsetBy: bounds.lowerBound) | |
let end = index(startIndex, offsetBy: bounds.upperBound) | |
return String(self[start..<end]) | |
} | |
subscript (bounds: PartialRangeUpTo<Int>) -> String { | |
let end = index(startIndex, offsetBy: bounds.upperBound) | |
return String(self[startIndex..<end]) | |
} | |
subscript (bounds: PartialRangeThrough<Int>) -> String { | |
let end = index(startIndex, offsetBy: bounds.upperBound) | |
return String(self[startIndex...end]) | |
} | |
subscript (bounds: CountablePartialRangeFrom<Int>) -> String { | |
let start = index(startIndex, offsetBy: bounds.lowerBound) | |
return String(self[start..<endIndex]) | |
} | |
} | |
let string = "0123456789" | |
string[0...5] //=> "012345" | |
string[1...3] //=> "123" | |
string[3..<7] //=> "3456" | |
string[...4] //=> "01234 | |
string[..<4] //=> "0123" | |
string[4...] //=> "456789" | |
Create URL from a string / Create URL from a string literal | |
public extension String { | |
public var url: URL? { | |
return URL(string: self) | |
} | |
} | |
extension URL: ExpressibleByStringLiteral { | |
public init(stringLiteral value: String) { | |
guard let url = URL(string: value) else { | |
fatalError("\(value) is an invalid url") | |
} | |
self = url | |
} | |
public init(extendedGraphemeClusterLiteral value: String) { | |
self.init(stringLiteral: value) | |
} | |
public init(unicodeScalarLiteral value: String) { | |
self.init(stringLiteral: value) | |
} | |
} | |
if let url = "https://example.com".url { | |
} | |
let url: URL = "https://example.com" | |
Use Kotlin-like scope function | |
public protocol Appliable {} | |
public extension Appliable { | |
@discardableResult | |
public func apply(closure: (Self) -> Void) -> Self { | |
closure(self) | |
return self | |
} | |
} | |
public protocol Runnable {} | |
public extension Runnable { | |
@discardableResult | |
public func run<T>(closure: (Self) -> T) -> T { | |
return closure(self) | |
} | |
} | |
extension NSObject: Appliable {} | |
extension NSObject: Runnable {} | |
let view = UIView().apply { | |
$0.backgroundColor = .red | |
$0.frame = .init(x: 0, y: 0, width: 200, height: 200) | |
} | |
Throw an error by ??? if the value is nil | |
infix operator ??? | |
public func ???<T>(lhs: T?, | |
error: @autoclosure () -> Error) throws -> T { | |
guard let value = lhs else { throw error() } | |
return value | |
} | |
let value: String? = nil | |
struct OptionalError: Error {} | |
do { | |
let v = try value ??? OptionalError() | |
print(v) // unreachable | |
} catch { | |
print(error) //=> OptionalError | |
} | |
Throw an error if there is no key when getting the value from a dictionary | |
public struct DictionaryTryValueError: Error { | |
public init() {} | |
} | |
public extension Dictionary { | |
func tryValue(forKey key: Key, error: Error = DictionaryTryValueError()) throws -> Value { | |
guard let value = self[key] else { throw error } | |
return value | |
} | |
} | |
var dictionary: [String: Int] = [:] | |
do { | |
let value = try dictionary.tryValue(forKey: "foo") | |
print(value) // unreachable | |
} catch { | |
print(error) //=> DictionaryTryValueError | |
} | |
Create properties and methods accessible with .ex | |
public struct TargetedExtension<Base> { | |
let base: Base | |
init (_ base: Base) { | |
self.base = base | |
} | |
} | |
public protocol TargetedExtensionCompatible { | |
associatedtype Compatible | |
static var ex: TargetedExtension<Compatible>.Type { get } | |
var ex: TargetedExtension<Compatible> { get } | |
} | |
public extension TargetedExtensionCompatible { | |
public static var ex: TargetedExtension<Self>.Type { | |
return TargetedExtension<Self>.self | |
} | |
public var ex: TargetedExtension<Self> { | |
return TargetedExtension(self) | |
} | |
} | |
extension UIView: TargetedExtensionCompatible {} | |
private extension TargetedExtension where Base: UIView { | |
func foo() {} | |
} | |
UIView().ex.foo() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment