Protocol extension on an ObjC protocol - swift

I have an Objective-C protocol which is used by mostly objective-C objects and one or two Swift objects.
I would like to extend the protocol in Swift and add 2 functions. One to register for a notification and another to handle the notification.
If I add these
func registerForPresetLoadedNotification() {
NSNotificationCenter.defaultCenter().addObserver(self as AnyObject,
selector: #selector(presetLoaded(_:)),
name: kPresetLoadedNotificationName,
object: nil)
}
func presetLoaded(notification: NSNotification) {
}
I get an error on the #selector which says:
Argument of '#selector' refers to a method that is not exposed to Objective-C
If I then mark presetLoaded as #objc I get an error which says:
#objc can only be used with members of classes, #objc protocols, and concrete extensions of classes
I also cannot mark the protocol extension as #objc
When I create the Objective-C protocol as a Swift protocol I get the same error.
Is there a way to achieve this that will work for Objective-C and Swift classes that use the protocol?

Indeed, you can't really mark a function of a protocol extension as #objc (or dynamic, which is equivalent by the way). Only methods of a class are allowed to be dispatched by Objective-C runtime.
In your particular case, if you really want to make it through protocol extension, I can propose the following solution (assuming your original protocol is named ObjcProtocol).
Let's make a wrapper for our notification handler:
final class InternalNotificationHandler {
private let source: ObjcProtocol
init(source: ObjcProtocol) {
// We require source object in case we need access some properties etc.
self.source = source
}
#objc func presetLoaded(notification: NSNotification) {
// Your notification logic here
}
}
Now we need extend our ObjcProtocol to introduce required logic
import Foundation
import ObjectiveC
internal var NotificationAssociatedObjectHandle: UInt8 = 0
extension ObjcProtocol {
// This stored variable represent a "singleton" concept
// But since protocol extension can only have stored properties we save it via Objective-C runtime
private var notificationHandler: InternalNotificationHandler {
// Try to an get associated instance of our handler
guard let associatedObj = objc_getAssociatedObject(self, &NotificationAssociatedObjectHandle)
as? InternalNotificationHandler else {
// If we do not have any associated create and store it
let newAssociatedObj = InternalNotificationHandler(source: self)
objc_setAssociatedObject(self,
&NotificationAssociatedObjectHandle,
newAssociatedObj,
objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return newAssociatedObj
}
return associatedObj
}
func registerForPresetLoadedNotification() {
NSNotificationCenter.defaultCenter().addObserver(self,
selector: #selector(notificationHandler.presetLoaded(_:)),
name: kPresetLoadedNotificationName,
object: self)
}
func unregisterForPresetLoadedNotification() {
// Clear notification observer and associated objects
NSNotificationCenter.defaultCenter().removeObserver(self,
name: kPresetLoadedNotificationName,
object: self)
objc_removeAssociatedObjects(self)
}
}
I know this might look not so elegant, so I'd really consider changing a core approach.
One note: You do might want to restrict your protocol extension
extension ObjcProtocol where Self: SomeProtocolOrClass

I found a way to do it :) Just avoid #objc all together :D
//Adjusts UITableView content height when keyboard show/hide
public protocol KeyboardObservable: NSObjectProtocol {
func registerForKeyboardEvents()
func unregisterForKeyboardEvents()
}
extension KeyboardObservable where Self: UITableView {
public func registerForKeyboardEvents() {
let defaultCenter = NotificationCenter.default
var tokenShow: NSObjectProtocol!
tokenShow = defaultCenter.addObserver(forName: .UIKeyboardDidShow, object: nil, queue: nil) { [weak self] (notification) in
guard self != nil else {
defaultCenter.removeObserver(tokenShow)
return
}
self!.keyboardWilShow(notification as NSNotification)
}
var tokenHide: NSObjectProtocol!
tokenHide = defaultCenter.addObserver(forName: .UIKeyboardWillHide, object: nil, queue: nil) { [weak self] (notification) in
guard self != nil else {
defaultCenter.removeObserver(tokenHide)
return
}
self!.keyboardWilHide(notification as NSNotification)
}
}
private func keyboardDidShow(_ notification: Notification) {
let rect = ((notification as NSNotification).userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
let height = rect.height
var insets = UIEdgeInsetsMake(0, 0, height, 0)
insets.top = contentInset.top
contentInset = insets
scrollIndicatorInsets = insets
}
private func keyboardWillHide(_ notification: Notification) {
var insets = UIEdgeInsetsMake(0, 0, 0, 0)
insets.top = contentInset.top
UIView.animate(withDuration: 0.3) {
self.contentInset = insets
self.scrollIndicatorInsets = insets
}
}
public func unregisterForKeyboardEvents() {
NotificationCenter.default.removeObserver(self)
}
}
Example
class CreateStudentTableView: UITableView, KeyboardObservable {
init(frame: CGRect, style: UITableViewStyle) {
super.init(frame: frame, style: style)
registerForKeyboardEvents()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Related

Observe interface orientation from the View

I was using NotificationCenter to observe interfaceOrientation change by UIApplication.willChangeStatusBarOrientationNotification. But UIApplication.willChangeStatusBarOrientationNotification is now marked deprecated. It is suggested to use viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator, but this is the method called from the ViewController. I have a subclass of UIView that needs this internally, and Im exposing this View as a framework. I would like to be able to still maintain this functionality without the need to be explicitly called from the ViewController. Is there another option to achieve this directly inside View? I tried observing UIWindowScene.interfaceOrientation with KVO, but this didn't work.
public extension Notification.Name {
static let willChangeStatusBarOrientationNotification = UIApplication.willChangeStatusBarOrientationNotification
}
...
#objc private func statusBarOrientationWillChange(notification: Notification) {
if let orientationRawValue = notification.userInfo?[UIApplication.statusBarOrientationUserInfoKey] as? Int, let orientation = AVCaptureVideoOrientation(rawValue: orientationRawValue) {
configureVideoOrientation(interfaceOrientation: orientation)
updateSceneInformation()
}
}
...
private func initialize() {
NotificationCenter.default.addObserver(self, selector: #selector(statusBarOrientationWillChange(notification:)), name: .willChangeStatusBarOrientationNotification, object: nil)
}
Try this. It seems to only be available as a post-orientation-change notification and not a pre-orientation-change notification, but it should not produce a deprecation warning. Note that the device orientation is of the UIDeviceOrientation type, not the UIInterfaceOrientation type.
public class MyView: UIView {
public override init(frame: CGRect) {
super.init(frame: frame)
NotificationCenter.default.
addObserver(self,
selector: #selector(deviceOrientationDidChange),
name: UIDevice.orientationDidChangeNotification,
object: nil)
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
#objc
private func deviceOrientationDidChange(_ notification: Notification) {
guard UIDevice.current.orientation.isValidInterfaceOrientation,
let orientation = AVCaptureVideoOrientation(rawValue: UIDevice.current.orientation.rawValue)
else { return }
configureVideoOrientation(interfaceOrientation: orientation)
updateSceneInformation()
}
}

Swift constrained Protocol for a subclass and superclass

I wanted to implement my own HUD for a UIViewCntroller and a UIView, so I did this:
protocol ViewHudProtocol {
func showLoadingView()
func hideLoadingView()
}
extension ViewHudProtocol where Self: UIView {
func showLoadingView() { //Show HUD by adding a custom UIView to self.}
}
func hideLoadingView() {
}
}
Now I can easily adopt ViewHudProtocol on any UIView to call showLoadingView and hideLoadingView. The problem is I want to use the same protocol for UIViewController, so I did this:
extension ViewHudProtocol where Self: UIViewController {
func showLoadingView() {
self.view.showLoadingView() //Error: UIView has no member showLoadingView
}
func hideLoadingView() {
self.view.hideLoadingView() //Error: UIView has no member hideLoadingView
}
}
I agree to the error that UIView has not adopted the protocol yet. So I did this:
extension UIView: ViewHudProtocol {}
And it works. Is there a better way to do this? I mean it feels wrong to extend every view with ViewHudProtocol, where not all of them will use it. If I could do something like, "only adopt ViewHudProtocol implicitly for a UIView, if its UIViewController demands for it. Else you could adopt ViewHUDProtocol manually on any UIView when required."
I would solve this with the following approach, using associatedtype, defined only for needed views and/or controllers (tested in Xcode 11.2 / swift 5.1):
protocol ViewHudProtocol {
associatedtype Content : ViewHudProtocol
var content: Self.Content { get }
func showLoadingView()
func hideLoadingView()
}
extension ViewHudProtocol where Self: UIView {
var content: some ViewHudProtocol {
return self
}
func showLoadingView() { //Show HUD by adding a custom UIView to self.}
}
func hideLoadingView() {
}
}
extension ViewHudProtocol where Self: UIViewController {
func showLoadingView() {
self.content.showLoadingView() //NO Error
}
func hideLoadingView() {
self.content.hideLoadingView() //NO Error
}
}
//Usage
extension UITableView: ViewHudProtocol { // only for specific view
}
extension UITableViewController: ViewHudProtocol { // only for specific controller
var content: some ViewHudProtocol {
return self.tableView
}
}
The problem
So you want to constraint the conformance of a UIViewController to the protocol ViewHudProtocol only when the UIViewController.view property conforms to ViewHudProtocol.
I am afraid this is not possible.
Understanding the problem
Let's have a better look at your problem
You have 2 types (UIView and UIViewController) and you want to add to both the same functionalities
func showLoadingView()
func hideLoadingView()
What Mick West teaches us
This kind of scenario is somehow similar to what Mick West faced during the development of the Tony Hawks series Mick West and an elegant solution is described in its article Evolve your hierarchy.
Solution
We can apply that approach to your problem and here's the solution
struct HudViewComponent {
let view: UIView
private let hud: UIView
init(view: UIView) {
self.view = view
self.hud = UIView(frame: view.frame)
self.hud.isHidden = true
self.view.addSubview(hud)
}
func showLoadingView() {
self.hud.isHidden = false
}
func hideLoadingView() {
self.hud.isHidden = true
}
}
protocol HasHudViewComponent {
var hidViewComponent: HudViewComponent { get }
}
extension HasHudViewComponent {
func showLoadingView() {
hidViewComponent.showLoadingView()
}
func hideLoadingView() {
hidViewComponent.hideLoadingView()
}
}
That's it, now you can add the hud functionalities to any Type conforming to HasHudViewComponent.
class SomeView: UIView, HasHudViewComponent {
lazy var hidViewComponent: HudViewComponent = { return HudViewComponent(view: self) }()
}
or
class MyViewController: UIViewController, HasHudViewComponent {
lazy var hidViewComponent: HudViewComponent = { return HudViewComponent(view: self.view) }()
}
Considerations
As you can see the idea is to thinking in terms of components.
You build a component (HudViewComponent) with your hud functionalities. The component only asks for the minimum requirements: it needs a UIView.
Next you define the HasHudViewComponent which states that the current type has a HudViewComponent property.
Finally you can add your hud functionalities to any Type which has a view (UIView, UIViewController, ...) simply conforming your type to HasHudViewComponent.
Notes
You asked an interesting question and I know this does not answers 100% what you were looking for, but by a practical point of view it should provides you with a tool to achieve what you need.
I would have taken this approach:
Create a UIView Class,
setup the view
Declare a shared object.
A function to show the view
A function to remove the view. and then call it in view controllers as IndicatorView.shared.show() , IndicatorView.shared.hide()
import Foundation
import UIKit
import Lottie
class IndicatorView : UIView {
static let shared = IndicatorView()
var loadingAnimation : AnimationView = {
let lottieView = AnimationView()
lottieView.translatesAutoresizingMaskIntoConstraints = false
lottieView.layer.masksToBounds = true
return lottieView
}()
var loadingLabel : UILabel = {
let label = UILabel()
label.textColor = .white
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont(name: "SegoeUI", size: 12)
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
translatesAutoresizingMaskIntoConstraints = false
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func show() {
setupLoadingView()
self.alpha = 0
UIView.animate(withDuration: 0.5, animations: {
self.isHidden = false
self.alpha = 1
}, completion: nil)
applyLottieAnimation()
}
public func hide() {
self.alpha = 1
UIView.animate(withDuration: 0.5, animations: {
self.alpha = 0
}, completion: { _ in
self.isHidden = true
self.removeFromSuperview()
}) }
private func setupLoadingView() {
let controller = UIApplication.shared.keyWindow!.rootViewController!
controller.view.addSubview(self)
//setup your views here
self.setNeedsLayout()
self.reloadInputViews()
}
}
For this particular scenario, a Decorator would work better, and result in a better design:
final class HUDDecorator {
private let view: UIView
init(_ view: UIView) {
self.view = view
}
func showLoadingView() {
// add the spinner
}
func hideLoadingView() {
// remove the spinner
}
}
Using the Decorator would then be as easy as declaring a property for it:
class MyViewController: UIViewController {
lazy var hudDecorator = HUDDecorator(view)
}
This will allow any controller to decide if it wants support for showing/hiding a loading view by simply exposing this property.
Protocols are too invasive for simple tasks like enhancing the looks on a UI component, and they have the disadvantage of forcing all views of a certain class to expose the protocol functionality, while the Decorator approach allows you to decide which view instances to receive the functionality.

"Extensions must not contain stored properties" preventing me from refactoring code

I have a 13 lines func that is repeated in my app in every ViewController, which sums to a total of 690 lines of code across the entire project!
/// Adds Menu Button
func addMenuButton() {
let menuButton = UIButton(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
let menuImage = UIImage(named: "MenuWhite")
menuButton.setImage(menuImage, for: .normal)
menuButton.addTarget(self, action: #selector(menuTappedAction), for: .touchDown)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: menuButton)
}
/// Launches the MenuViewController
#objc func menuTappedAction() {
coordinator?.openMenu()
}
for menuTappedAction function to work, I have to declare a weak var like this:
extension UIViewController {
weak var coordinator: MainCoordinator?
But by doing this I get error Extensions must not contain stored properties
What I tried so far:
1) Removing the weak keyword will cause conflicts in all my app.
2) Declaring this way:
weak var coordinator: MainCoordinator?
extension UIViewController {
Will silence the error but the coordinator will not perform any action. Any suggestion how to solve this problem?
You can move your addMenuButton() function to a protocol with a protocol extension. For example:
#objc protocol Coordinated: class {
var coordinator: MainCoordinator? { get set }
#objc func menuTappedAction()
}
extension Coordinated where Self: UIViewController {
func addMenuButton() {
let menuButton = UIButton(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
let menuImage = UIImage(named: "MenuWhite")
menuButton.setImage(menuImage, for: .normal)
menuButton.addTarget(self, action: #selector(menuTappedAction), for: .touchDown)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: menuButton)
}
}
Unfortunately, you can't add #objc methods to class extensions (see: this stackoverflow question), so you'll still have to setup your view controllers like this:
class SomeViewController: UIViewController, Coordinated {
weak var coordinator: MainCoordinator?
/// Launches the MenuViewController
#objc func menuTappedAction() {
coordinator?.openMenu()
}
}
It'll save you some code, and it will allow you to refactor the bigger function addMenuButton(). Hope this helps!
For it to work in an extension you have to make it computed property like so : -
extension ViewController {
// Make it computed property
weak var coordinator: MainCoordinator? {
return MainCoordinator()
}
}
You could use objc associated objects.
extension UIViewController {
private struct Keys {
static var coordinator = "coordinator_key"
}
private class Weak<V: AnyObject> {
weak var value: V?
init?(_ value: V?) {
guard value != nil else { return nil }
self.value = value
}
}
var coordinator: Coordinator? {
get { (objc_getAssociatedObject(self, &Keys.coordinator) as? Weak<Coordinator>)?.value }
set { objc_setAssociatedObject(self, &Keys.coordinator, Weak(newValue), .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}
}
This happens because an extension is not a class, so it can't contain stored properties. Even if they are weak properties.
With that in mind, you have two main options:
The swift way: Protocol + Protocol Extension
The nasty objc way: associated objects
Option 1: use protocol and a protocol extension:
1.1. Declare your protocol
protocol CoordinatorProtocol: class {
var coordinator: MainCoordinator? { get set }
func menuTappedAction()
}
1.2. Create a protocol extension so you can pre-implement the addMenuButton() method
extension CoordinatorProtocol where Self: UIViewController {
func menuTappedAction() {
// Do your stuff here
}
}
1.3. Declare the weak var coordinator: MainCoordinator? in the classes that will be adopting this protocol. Unfortunately, you can't skip this
class SomeViewController: UIViewController, CoordinatorProtocol {
weak var coordinator: MainCoordinator?
}
Option 2: use objc associated objects (NOT RECOMMENDED)
extension UIViewController {
private struct Keys {
static var coordinator = "coordinator_key"
}
public var coordinator: Coordinator? {
get { objc_getAssociatedObject(self, &Keys.coordinator) as? Coordinator }
set { objc_setAssociatedObject(self, &Keys.coordinator, newValue, .OBJC_ASSOCIATION_ASSIGN) }
}
}
You can do it through subclassing
class CustomVC:UIViewController {
weak var coordinator: MainCoordinator?
func addMenuButton() {
let menuButton = UIButton(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
let menuImage = UIImage(named: "MenuWhite")
menuButton.setImage(menuImage, for: .normal)
menuButton.addTarget(self, action: #selector(menuTappedAction), for: .touchDown)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: menuButton)
}
/// Launches the MenuViewController
#objc func menuTappedAction() {
coordinator?.openMenu()
}
}
class MainCoordinator {
func openMenu() {
}
}
class ViewController: CustomVC {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
Use a NSMapTable to create a state container for your extension, but make sure that you specify to use weak references for keys.
Create a class in which you want to store the state. Let's call it ExtensionState and then create a map as a private field in extension file.
private var extensionStateMap: NSMapTable<TypeBeingExtended, ExtensionState> = NSMapTable.weakToStrongObjects()
Then your extension can be something like this.
extension TypeBeingExtended {
private func getExtensionState() -> ExtensionState {
var state = extensionStateMap.object(forKey: self)
if state == nil {
state = ExtensionState()
extensionStateMap.setObject(state, forKey: self)
}
return state
}
func toggleFlag() {
var state = getExtensionState()
state.flag = !state.flag
}
}
This works in iOS and macOS development, but not on server side Swift as there is no NSMapTable there.

Update the viewController from the controller in MVC with SWIFT

I'm creating an app with swift. The app get weather from openweathermap.com api in the class WeatherDataModel then when the data are loaded, the model ask the viewController to update the datas
I'm on Xcode 10.2.1 with swift 5
I've create a protocol called in the model to update the data but the updateDisplayDelegate?.updateWeatherDataOnDisplay() is always nil and even if I get the data from the JSON in the console it won't update on the screen
class WeatherDataModel {
var updateDisplayDelegate: ProtocolUpdateDisplay?
func updateWeaterData(json : JSON) {
updateDisplayDelegate?.updateWeatherDataOnDisplay()
}
}
public protocol ProtocolUpdateDisplay {
func updateWeatherDataOnDisplay()
}
class MainViewController: UIViewController {
let weatherDataModel = WeatherDataModel()
override func viewDidLoad() {
super.viewDidLoad()
weatherDataModel.updateDisplayDelegate = self
}
extension MainViewController: ProtocolUpdateDisplay {
func updateWeatherDataOnDisplay() {
cityLabel.text = weatherDataModel.city
tempLabel.text = weatherDataModel.temperature
weatherIcon.image = UIImage(named: weatherDataModel.weatherIconName)
}
}
You should not use delegation pattern for model. Consider using notification:
func updateWeaterData(json : JSON) {
NotificationCenter.default.post(Notification(name: Notification.Name("WeatherDidUpdate")))
}
and observe in any controller you want to respond to this notification:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(updateWeatherDataOnDisplay(_:)), name: Notification.Name("WeatherDidUpdate"), object: nil)
}
#objc func updateWeatherDataOnDisplay(_ notification: Notification) {
cityLabel.text = weatherDataModel.city
tempLabel.text = weatherDataModel.temperature
weatherIcon.image = UIImage(named: weatherDataModel.weatherIconName)
}
and remove observer at last:
deinit {
NotificationCenter.default.removeObserver(self)
}

Load a UIView from nib in Swift

Here is my Objective-C code which I'm using to load a nib for my customised UIView:
-(id)init{
NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:#"myXib" owner:self options:nil];
return [subviewArray objectAtIndex:0];
}
What is the equivalent code in Swift?
My contribution:
extension UIView {
class func fromNib<T: UIView>() -> T {
return Bundle(for: T.self).loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T
}
}
Then call it like this:
let myCustomView: CustomView = UIView.fromNib()
..or even:
let myCustomView: CustomView = .fromNib()
Original Solution
I created a XIB and a class named SomeView (used the same name for
convenience and readability). I based both on a UIView.
In the XIB, I changed the "File's Owner" class to SomeView (in the identity inspector).
I created a UIView outlet in SomeView.swift, linking it to the top level view in the XIB file (named it "view" for convenience). I then added other outlets to other controls in the XIB file as needed.
in SomeView.swift, I loaded the XIB inside the "init with code" initializer. There is no need to assign anything to "self". As soon as the XIB is loaded, all outlets are connected, including the top level view. The only thing missing, is to add the top view to the view hierarchy:
.
class SomeView: UIView {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
NSBundle.mainBundle().loadNibNamed("SomeView", owner: self, options: nil)
self.addSubview(self.view); // adding the top level view to the view hierarchy
}
...
}
Note that this way I get a class that loads itself from nib. I could then use SomeView as a class whenever UIView could be used in the project (in interface builder or programmatically).
Update - using Swift 3 syntax
Loading a xib in the following extension is written as an instance method, which can then be used by an initializer like the one above:
extension UIView {
#discardableResult // 1
func fromNib<T : UIView>() -> T? { // 2
guard let contentView = Bundle(for: type(of: self)).loadNibNamed(String(describing: type(of: self)), owner: self, options: nil)?.first as? T else { // 3
// xib not loaded, or its top view is of the wrong type
return nil
}
self.addSubview(contentView) // 4
contentView.translatesAutoresizingMaskIntoConstraints = false // 5
contentView.layoutAttachAll(to: self) // 6
return contentView // 7
}
}
Using a discardable return value since the returned view is mostly of no interest to caller when all outlets are already connected.
This is a generic method that returns an optional object of type UIView. If it fails to load the view, it returns nil.
Attempting to load a XIB file with the same name as the current class instance. If that fails, nil is returned.
Adding the top level view to the view hierarchy.
This line assumes we're using constraints to layout the view.
This method adds top, bottom, leading & trailing constraints - attaching the view to "self" on all sides (See: https://stackoverflow.com/a/46279424/2274829 for details)
Returning the top level view
And the caller method might look like this:
final class SomeView: UIView { // 1.
required init?(coder aDecoder: NSCoder) { // 2 - storyboard initializer
super.init(coder: aDecoder)
fromNib() // 5.
}
init() { // 3 - programmatic initializer
super.init(frame: CGRect.zero) // 4.
fromNib() // 6.
}
// other methods ...
}
SomeClass is a UIView subclass that loads its content from a SomeClass.xib file. The "final" keyword is optional.
An initializer for when the view is used in a storyboard (remember to use SomeClass as the custom class of your storyboard view).
An initializer for when the view is created programmatically (i.e.: "let myView = SomeView()").
Using an all-zeros frame since this view is laid out using auto-layout.
Note that an "init(frame: CGRect) {..}" method is not created independently, since auto-layout is used exclusively in our project.
& 6. Loading the xib file using the extension.
Credit: Using a generic extension in this solution was inspired by Robert's answer below.
Edit
Changing "view" to "contentView" to avoid confusion. Also changed the array subscript to ".first".
Now being able to return -> Self in swift helps simplify this a bit. Last confirmed on Swift 5.
extension UIView {
class func fromNib(named: String? = nil) -> Self {
let name = named ?? "\(Self.self)"
guard
let nib = Bundle.main.loadNibNamed(name, owner: nil, options: nil)
else { fatalError("missing expected nib named: \(name)") }
guard
/// we're using `first` here because compact map chokes compiler on
/// optimized release, so you can't use two views in one nib if you wanted to
/// and are now looking at this
let view = nib.first as? Self
else { fatalError("view of type \(Self.self) not found in \(nib)") }
return view
}
}
If your .xib file and subclass share the same name, you can use:
let view = CustomView.fromNib()
If you have a custom name, use:
let view = CustomView.fromNib(named: "special-case")
NOTE:
If you're getting the error "view of type YourType not found in.." then you haven't set the view's class in the .xib file
Select your view in the .xib file, and press cmd + opt + 4 and in the class input, enter your class
Swift 4 - 5.1 Protocol Extensions
public protocol NibInstantiatable {
static func nibName() -> String
}
extension NibInstantiatable {
static func nibName() -> String {
return String(describing: self)
}
}
extension NibInstantiatable where Self: UIView {
static func fromNib() -> Self {
let bundle = Bundle(for: self)
let nib = bundle.loadNibNamed(nibName(), owner: self, options: nil)
return nib!.first as! Self
}
}
Adoption
class MyView: UIView, NibInstantiatable {
}
This implementation assumes that the Nib has the same name as the UIView class. Ex. MyView.xib. You can modify this behavior by implementing nibName() in MyView to return a different name than the default protocol extension implementation.
In the xib the files owner is MyView and the root view class is MyView.
Usage
let view = MyView.fromNib()
try following code.
var uiview :UIView?
self.uiview = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)[0] as? UIView
Edit:
import UIKit
class TestObject: NSObject {
var uiview:UIView?
init() {
super.init()
self.uiview = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)[0] as? UIView
}
}
If you have a lot of custom views in your project you can create class like UIViewFromNib
Swift 2.3
class UIViewFromNib: UIView {
var contentView: UIView!
var nibName: String {
return String(self.dynamicType)
}
//MARK:
override init(frame: CGRect) {
super.init(frame: frame)
loadViewFromNib()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
loadViewFromNib()
}
//MARK:
private func loadViewFromNib() {
contentView = NSBundle.mainBundle().loadNibNamed(nibName, owner: self, options: nil)[0] as! UIView
contentView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
contentView.frame = bounds
addSubview(contentView)
}
}
Swift 5
class UIViewFromNib: UIView {
var contentView: UIView!
var nibName: String {
return String(describing: type(of: self))
}
//MARK:
override init(frame: CGRect) {
super.init(frame: frame)
loadViewFromNib()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
loadViewFromNib()
}
//MARK:
func loadViewFromNib() {
let bundle = Bundle(for: UIViewFromNib.self)
contentView = UINib(nibName: nibName, bundle: bundle).instantiate(withOwner: self).first as? UIView
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
contentView.frame = bounds
addSubview(contentView)
}
}
And in every class just inherit from UIViewFromNib, also you can override nibName property if .xib file has different name:
class MyCustomClass: UIViewFromNib {
}
I achieved this with Swift by the following code:
class Dialog: UIView {
#IBOutlet var view:UIView!
override init(frame: CGRect) {
super.init(frame: frame)
self.frame = UIScreen.mainScreen().bounds
NSBundle.mainBundle().loadNibNamed("Dialog", owner: self, options: nil)
self.view.frame = UIScreen.mainScreen().bounds
self.addSubview(self.view)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Don't forget to connect your XIB view outlet to view outlet defined in swift. You can also set First Responder to your custom class name to start connecting any additional outlets.
Hope this helps!
Tested in Xcode 7 beta 4 , Swift 2.0 and iOS9 SDK .
The following code will assign xib to the uiview.
You can able to use this custom xib view in storyboard and access the IBOutlet object also.
import UIKit
#IBDesignable class SimpleCustomView:UIView
{
var view:UIView!;
#IBOutlet weak var lblTitle: UILabel!
#IBInspectable var lblTitleText : String?
{
get{
return lblTitle.text;
}
set(lblTitleText)
{
lblTitle.text = lblTitleText!;
}
}
override init(frame: CGRect) {
super.init(frame: frame)
loadViewFromNib ()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
loadViewFromNib ()
}
func loadViewFromNib() {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "SimpleCustomView", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
view.frame = bounds
view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
self.addSubview(view);
}
}
Access customview programatically
self.customView = SimpleCustomView(frame: CGRectMake(100, 100, 200, 200))
self.view.addSubview(self.customView!);
Source code - https://github.com/karthikprabhuA/CustomXIBSwift
Building on the above solutions.
This will work across all project bundles and no need for generics when calling fromNib().
Swift 2
extension UIView {
public class func fromNib() -> Self {
return fromNib(nil)
}
public class func fromNib(nibName: String?) -> Self {
func fromNibHelper<T where T : UIView>(nibName: String?) -> T {
let bundle = NSBundle(forClass: T.self)
let name = nibName ?? String(T.self)
return bundle.loadNibNamed(name, owner: nil, options: nil)?.first as? T ?? T()
}
return fromNibHelper(nibName)
}
}
Swift 3
extension UIView {
public class func fromNib() -> Self {
return fromNib(nibName: nil)
}
public class func fromNib(nibName: String?) -> Self {
func fromNibHelper<T>(nibName: String?) -> T where T : UIView {
let bundle = Bundle(for: T.self)
let name = nibName ?? String(describing: T.self)
return bundle.loadNibNamed(name, owner: nil, options: nil)?.first as? T ?? T()
}
return fromNibHelper(nibName: nibName)
}
}
Can be used like this:
let someView = SomeView.fromNib()
Or like this:
let someView = SomeView.fromNib("SomeOtherNibFileName")
Swift 4
Don't forget to write ".first as? CustomView".
if let customView = Bundle.main.loadNibNamed("myXib", owner: self, options: nil)?.first as? CustomView {
self.view.addSubview(customView)
}
If you want to use anywhere
The Best Solution is Robert Gummesson's answer.
extension UIView {
class func fromNib<T: UIView>() -> T {
return Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T
}
}
Then call it like this:
let myCustomView: CustomView = UIView.fromNib()
I prefer this solution (based on the answer if #GK100):
I created a XIB and a class named SomeView (used the same name for convenience and readability). I based both on a UIView.
In the XIB, I changed the "File's Owner" class to SomeView (in the identity inspector).
I created a UIView outlet in SomeView.swift, linking it to the top level view in the XIB file (named it "view" for convenience). I then added other outlets to other controls in the XIB file as needed.
In SomeView.swift, I loaded the XIB inside the init or init:frame: CGRect initializer. There is no need to assign anything to "self". As soon as the XIB is loaded, all outlets are connected, including the top level view. The only thing missing, is to add the top view to the view hierarchy:
class SomeView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
NSBundle.mainBundle().loadNibNamed("SomeObject", owner: self, options: nil)
self.addSubview(self.view); // adding the top level view to the view hierarchy
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
NSBundle.mainBundle().loadNibNamed("SomeObject", owner: self, options: nil)
self.addSubview(self.view); // adding the top level view to the view hierarchy
}
...
}
A nice way to do this with Swift is to use an enum.
enum Views: String {
case view1 = "View1" // Change View1 to be the name of your nib
case view2 = "View2" // Change View2 to be the name of another nib
func getView() -> UIView? {
return NSBundle.mainBundle().loadNibNamed(self.rawValue, owner: nil, options: nil).first as? UIView
}
}
Then in your code you can simply use:
let view = Views.view1.getView()
Updated for Swift 5
Somewhere define below:
extension UIView {
public class func fromNib<T: UIView>() -> T {
let name = String(describing: Self.self);
guard let nib = Bundle(for: Self.self).loadNibNamed(
name, owner: nil, options: nil)
else {
fatalError("Missing nib-file named: \(name)")
}
return nib.first as! T
}
}
And use above like:
let view: MyCustomView = .fromNib();
Which will search in same bundle as MyCustomView, then load MyCustomView.nib file (if file exists, and is added to project).
let subviewArray = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)
return subviewArray[0]
Swift 5 - Clean and easy to use extension
[Copy Paste from production project]
//
// Refactored by Essam Mohamed Fahmi.
//
import UIKit
extension UIView
{
static var nib: UINib
{
return UINib(nibName: "\(self)", bundle: nil)
}
static func instantiateFromNib() -> Self?
{
return nib.instantiate() as? Self
}
}
extension UINib
{
func instantiate() -> Any?
{
return instantiate(withOwner: nil, options: nil).first
}
}
Usage
let myCustomView: CustomView = .instantiateFromNib()
I just do this way :
if let myView = UINib.init(nibName: "MyView", bundle: nil).instantiate(withOwner: self)[0] as? MyView {
// Do something with myView
}
This sample uses the first view in the nib "MyView.xib" in the main bundle. But you can vary either the index, the nib name, or the bundle ( main by default ).
I used to awake views into the view init method or make generic methods as in the proposed answers above ( which are smart by the way ), but I don't do it anymore because I have noticed use cases are often different, and to cover all cases, generic methods become as complex as using the UINib.instantiate method.
I prefer to use a factory object, usually the ViewController that will use the view, or a dedicated factory object or view extension if the view needs to be used in multiple places.
In this example, a ViewController loads a view from nib.
The nib file can be changed to use different layouts for the same view class. ( This not nice code, it just illustrates the idea )
class MyViewController {
// Use "MyView-Compact" for compact version
var myViewNibFileName = "MyView-Standard"
lazy var myView: MyView = {
// Be sure the Nib is correct, or it will crash
// We don't want to continue with a wrong view anyway, so ! is ok
UINib.init(nibName: myViewNibFileName, bundle: nil).instantiate(withOwner: self)[0] as! MyView
}()
}
Swift 3 version of Logan's answer
extension UIView {
public class func fromNib(nibName: String? = nil) -> Self {
return fromNib(nibName: nibName, type: self)
}
public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T {
return fromNib(nibName: nibName, type: T.self)!
}
public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T? {
var view: T?
let name: String
if let nibName = nibName {
name = nibName
} else {
name = self.nibName
}
if let nibViews = Bundle.main.loadNibNamed(name, owner: nil, options: nil) {
for nibView in nibViews {
if let tog = nibView as? T {
view = tog
}
}
}
return view
}
public class var nibName: String {
return "\(self)".components(separatedBy: ".").first ?? ""
}
public class var nib: UINib? {
if let _ = Bundle.main.path(forResource: nibName, ofType: "nib") {
return UINib(nibName: nibName, bundle: nil)
} else {
return nil
}
}
}
Here is a clean and declarative way of programmatically loading a view using a protocol and protocol extension (Swift 4.2):
protocol XibLoadable {
associatedtype CustomViewType
static func loadFromXib() -> CustomViewType
}
extension XibLoadable where Self: UIView {
static func loadFromXib() -> Self {
let nib = UINib(nibName: "\(self)", bundle: Bundle(for: self))
guard let customView = nib.instantiate(withOwner: self, options: nil).first as? Self else {
// your app should crash if the xib doesn't exist
preconditionFailure("Couldn't load xib for view: \(self)")
}
return customView
}
}
And you can use this like so:
// don't forget you need a xib file too
final class MyView: UIView, XibLoadable { ... }
// and when you want to use it
let viewInstance = MyView.loadFromXib()
Some additional considerations:
Make sure your custom view's xib file has the view's Custom Class set (and outlets/actions set from there), not the File Owner's.
You can use this protocol/extension external to your custom view or internal. You may want to use it internally if you have some other setup work when initializing your view.
Your custom view class and xib file need to have the same name.
All you have to do is call init method in your UIView class.
Do it that way:
class className: UIView {
#IBOutlet var view: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
func setup() {
UINib(nibName: "nib", bundle: nil).instantiateWithOwner(self, options: nil)
addSubview(view)
view.frame = self.bounds
}
}
Now, if you want to add this view as a sub view in view controller, do it that way in view controller.swift file:
self.view.addSubview(className())
Similar to some of the answers above but a more consistent Swift3 UIView extension:
extension UIView {
class func fromNib<A: UIView> (nibName name: String, bundle: Bundle? = nil) -> A? {
let bundle = bundle ?? Bundle.main
let nibViews = bundle.loadNibNamed(name, owner: self, options: nil)
return nibViews?.first as? A
}
class func fromNib<T: UIView>() -> T? {
return fromNib(nibName: String(describing: T.self), bundle: nil)
}
}
Which gives the convenience of being able to load the class from a self named nib but also from other nibs/bundles.
You can do this via storyboard, just add proper constraints for view. You can do this easily by subclassing any view from your own let's say BaseView:
Objective-C
BaseView.h
/*!
#class BaseView
#discussion Base View for getting view from xibFile
#availability ios7 and later
*/
#interface BaseView : UIView
#end
BaseView.m
#import "BaseView.h"
#implementation BaseView
#pragma mark - Public
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self prepareView];
}
return self;
}
#pragma mark - LifeCycle
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self prepareView];
}
return self;
}
#pragma mark - Private
- (void)prepareView
{
NSArray *nibsArray = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil];
UIView *view = [nibsArray firstObject];
view.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:view];
[self addConstraintsForView:view];
}
#pragma mark - Add constraints
- (void)addConstraintsForView:(UIView *)view
{
[self addConstraints:#[[NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0],
[NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0],
[NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:0],
[NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:self attribute:NSLayoutAttributeRight
multiplier:1.0
constant:0]
]];
}
#end
Swift 4
import UIKit
class BaseView : UIView {
// MARK: - LifeCycle
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
prepareView()
}
override init(frame: CGRect) {
super.init(frame: frame)
prepareView()
}
internal class func xibName() -> String {
return String(describing: self)
}
// MARK: - Private
fileprivate func prepareView() {
let nameForXib = BaseView.xibName()
let nibs = Bundle.main.loadNibNamed(nameForXib, owner: self, options: nil)
if let view = nibs?.first as? UIView {
view.backgroundColor = UIColor.clear
view.translatesAutoresizingMaskIntoConstraints = false
addSubviewWithConstraints(view, offset: false)
}
}
}
UIView+Subview
public extension UIView {
// MARK: - UIView+Extensions
public func addSubviewWithConstraints(_ subview:UIView, offset:Bool = true) {
subview.translatesAutoresizingMaskIntoConstraints = false
let views = [
"subview" : subview
]
addSubview(subview)
var constraints = NSLayoutConstraint.constraints(withVisualFormat: offset ? "H:|-[subview]-|" : "H:|[subview]|", options: [.alignAllLeading, .alignAllTrailing], metrics: nil, views: views)
constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: offset ? "V:|-[subview]-|" : "V:|[subview]|", options: [.alignAllTop, .alignAllBottom], metrics: nil, views: views))
NSLayoutConstraint.activate(constraints)
}
}
I provide 2 variants how to add constraints - common one and within visual format language - select any you want :)
Also, by default assumed that xib name has same name as implementation class name. If no - just change xibName parameter.
If you subclass your view from BaseView - you can easily put any view and specify class in IB.
If you want the Swift UIView subclass to be entirely self contained, and have the ability to be instantiated using init or init(frame:) without exposing the implementation detail of using a Nib, then you can use a protocol extension to achieve this. This solution avoids the nested UIView hierarchy as suggested by many of the other solutions.
public class CustomView: UIView {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var valueLabel: UILabel!
public convenience init() {
self.init(frame: CGRect.zero)
}
public override convenience init(frame: CGRect) {
self.init(internal: nil)
self.frame = frame
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
fileprivate func commonInit() {
}
}
fileprivate protocol _CustomView {
}
extension CustomView: _CustomView {
}
fileprivate extension _CustomView {
// Protocol extension initializer - has the ability to assign to self, unlike
// class initializers. Note that the name of this initializer can be anything
// you like, here we've called it init(internal:)
init(internal: Int?) {
self = Bundle.main.loadNibNamed("CustomView", owner:nil, options:nil)![0] as! Self;
}
}
class func loadFromNib<T: UIView>() -> T {
let nibName = String(describing: self)
return Bundle.main.loadNibNamed(nibName, owner: nil, options: nil)![0] as! T
}
let nibs = Bundle.main.loadNibNamed("YourView", owner: nil, options: nil)
let shareView = nibs![0] as! ShareView
self.view.addSubview(shareView)
// Use this class as super view
import UIKit
class ViewWithXib: UIView {
func initUI() {}
private func xibSetup() {
let view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]
addSubview(view)
initUI()
}
private func loadViewFromNib() -> UIView {
let thisName = String(describing: type(of: self))
let view = Bundle(for: self.classForCoder).loadNibNamed(thisName, owner: self, options: nil)?.first as! UIView
return view
}
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
}
// Usages:
class HeaderView: ViewWithXib {
}
let header = HeaderView() // No need to load the view from nib, It will work
More powerful version based on Logan's answer
extension UIView {
public class func fromNib(nibName: String? = nil) -> Self {
return fromNib(nibName: nibName, type: self)
}
public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T {
return fromNib(nibName: nibName, type: T.self)!
}
public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T? {
var view: T?
let name: String
if let nibName = nibName {
name = nibName
} else {
name = self.nibName
}
if let nibViews = nibBundle.loadNibNamed(name, owner: nil, options: nil) {
if nibViews.indices.contains(nibIndex), let tog = nibViews[nibIndex] as? T {
view = tog
}
}
return view
}
public class var nibName: String {
return "\(self)".components(separatedBy: ".").first ?? ""
}
public class var nibIndex: Int {
return 0
}
public class var nibBundle: Bundle {
return Bundle.main
}
}
And you can use like
class BaseView: UIView {
override class var nibName: String { return "BaseView" }
weak var delegate: StandardStateViewDelegate?
}
class ChildView: BaseView {
override class var nibIndex: Int { return 1 }
}
The most convenient implementation. Here you need two methods, in order to return directly to the object of your class, not UIView.
viewId marked as a class, allowing override
Your .xib can contain more than one view of the top level, this situation is also
handled correctly.
extension UIView {
class var viewId: String {
return String(describing: self)
}
static func instance(from bundle: Bundle? = nil, nibName: String? = nil,
owner: Any? = nil, options: [AnyHashable : Any]? = nil) -> Self? {
return instancePrivate(from: bundle ?? Bundle.main,
nibName: nibName ?? viewId,
owner: owner,
options: options)
}
private static func instancePrivate<T: UIView>(from bundle: Bundle, nibName: String,
owner: Any?, options: [AnyHashable : Any]?) -> T? {
guard
let views = bundle.loadNibNamed(nibName, owner: owner, options: options),
let view = views.first(where: { $0 is T }) as? T else { return nil }
return view
}
}
Example:
guard let customView = CustomView.instance() else { return }
//Here customView has CustomView class type, not UIView.
print(customView is CustomView) // true
let bundle = Bundle(for: type(of: self))
let views = bundle.loadNibNamed("template", owner: self, options: nil)
self.view.addSubview(views?[0] as! UIView)
I prefer the below extension
extension UIView {
class var instanceFromNib: Self {
return Bundle(for: Self.self)
.loadNibNamed(String(describing: Self.self), owner: nil, options: nil)?.first as! Self
}
}
The difference between this and the top answered extension is you don't need to store it an constant or variable.
class TitleView: UIView { }
extension UIView {
class var instanceFromNib: Self {
return Bundle(for: Self.self)
.loadNibNamed(String(describing: Self.self), owner: nil, options: nil)?.first as! Self
}
}
self.navigationItem.titleView = TitleView.instanceFromNib
Robert Gummesson's Answer is perfect. But when we try to use it in SPM or framework it is not working.
I've modified like below to make it work.
internal class func fromNib<T: UIView>() -> T {
return Bundle.module.loadNibNamed(String(describing: T.self), owner: self, options: nil)![0] as! T
}