How to add a UIView over the Keyboard - iOS - iphone

I have been trying to display a toast message on iOS. What I did was when any notification comes, just I took the navigation controller view and added a subview for my toast message and displayed.
UIView *top_view = self.navigationController.view;
[top_view showToast:string];
Everything works fine. However my toast view is not adding over the keyboard(if the keyboard is at the front). Any idea what could be the problem... Little helps may save my time... Thanx..

You can display the toast by adding subview to your main window.
UIWindow *toastDisplaywindow = [[[UIApplication sharedApplication] delegate] window];;
for (UIWindow *testWindow in [[UIApplication sharedApplication] windows])
{
if (![[testWindow class] isEqual:[UIWindow class]])
{
self.toastDisplaywindow = testWindow;
break;
}
}
[toastDisplaywindow showToast:string];
If a keyboard is being displayed, it will be displayed as a separate window, above your usual main window. Hence a check made to find out if the keyboard is being displayed. If it is, then add the toast message on that window, else on the main window.
I found another method in this link, using which you can directly get to the UIView of the keyboard (If required).

You have to add your subview to:
UIWindow *window = [UIApplication sharedApplication].windows.lastObject;
which is on top of the keyboard.

Generally keyboard view is not part of your main window. it appears with new window when you get focused in any text field.
Try the following code to access your keyboard view.
[[[UIApplication sharedApplication] windows] objectAtIndex:1]
Remember, this will only work when you have keyboard on your screen.

Another way is to add a custom UIWindow, then setting it's WindowLevel to +1 of the last window.
Something like this
NSArray *windows = [[UIApplication sharedApplication] windows];
UIWindow *lastWindow = (UIWindow *)[windows lastObject];
myWindow.windowLevel = lastWindow.windowLevel + 1;
Take a look to this topic
https://forums.developer.apple.com/thread/16375

Update for Swift3
UIApplication.shared.windows.last

in iOS9 the answer by Adithya is not work,
UIWindow *window = [UIApplication sharedApplication].windows.lastObject;
work well

Try to add the view as a subview of the following class. This code snippet works for iOS 14 and above. Not sure about older versions. Reference: Toaster Github repo
Use it like:
ToastWindow.shared.addSubView(/your_view/)
public final class ToastWindow: UIWindow {
public static let shared = ToastWindow(frame: UIScreen.main.bounds, mainWindow: UIApplication.shared.connectedScenes.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }.first { $0.isKeyWindow } )
override public var rootViewController: UIViewController? {
get {
guard !self.isShowing else {
isShowing = false
return nil
}
guard let firstWindow = UIApplication.shared.delegate?.window else { return nil }
return firstWindow is ToastWindow ? nil : firstWindow?.rootViewController
}
set { /* Do nothing */ }
}
override public var isHidden: Bool {
willSet { isShowing = true }
didSet { isShowing = false }
}
/// Will not return `rootViewController` while this value is `true`. Needed for iOS 13.
private var isShowing = false
/// Returns original subviews. `ToastWindow` overrides `addSubview()` to add a subview to the
/// top window instead itself.
private var originalSubviews = NSPointerArray.weakObjects()
private weak var mainWindow: UIWindow?
// MARK: - Init
public init(frame: CGRect, mainWindow: UIWindow?) {
super.init(frame: frame)
self.mainWindow = mainWindow
self.isUserInteractionEnabled = false
self.gestureRecognizers = nil
self.windowLevel = .init(rawValue: .greatestFiniteMagnitude)
let keyboardWillShowName = UIWindow.keyboardWillShowNotification
let keyboardDidHideName = UIWindow.keyboardDidHideNotification
self.backgroundColor = .clear
self.isHidden = false
NotificationCenter.default.addObserver( self, selector: #selector(self.keyboardWillShow),
name: keyboardWillShowName,
object: nil )
NotificationCenter.default.addObserver( self, selector: #selector(self.keyboardDidHide),
name: keyboardDidHideName,
object: nil )
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented: please use ToastWindow.shared")
}
override public func addSubview(_ view: UIView) {
super.addSubview(view)
self.originalSubviews.addPointer(Unmanaged.passUnretained(view).toOpaque())
self.topWindow()?.addSubview(view)
}
public override func becomeKey() {
super.becomeKey()
mainWindow?.makeKey()
}
// MARK: - Keyboard methods
#objc private func keyboardWillShow() {
guard let topWindow = self.topWindow(),
let subviews = self.originalSubviews.allObjects as? [UIView] else { return }
for subview in subviews {
topWindow.addSubview(subview)
}
}
#objc private func keyboardDidHide() {
guard let subviews = self.originalSubviews.allObjects as? [UIView] else { return }
for subview in subviews {
super.addSubview(subview)
}
}
/// Returns top window that isn't self
private func topWindow() -> UIWindow? {
if let window = UIApplication.shared.windows.last(where: {
ToastWindowKeyboardObserver.shared.didKeyboardShow || $0.isOpaque
}), window !== self {
return window
}
return nil
}
}
final fileprivate class ToastWindowKeyboardObserver {
static let shared = ToastWindowKeyboardObserver()
private(set) var didKeyboardShow: Bool = false
private(set) var keyboardHeight = 0.0
private init() {
let keyboardWillShowName = UIWindow.keyboardWillShowNotification
let keyboardDidHideName = UIWindow.keyboardDidHideNotification
NotificationCenter.default.addObserver( self, selector: #selector(keyboardWillShow),
name: keyboardWillShowName,
object: nil )
NotificationCenter.default.addObserver( self, selector: #selector(keyboardDidHide),
name: keyboardDidHideName,
object: nil )
}
#objc func keyboardWillShow(_ notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
keyboardHeight = keyboardRectangle.height
}
didKeyboardShow = true
}
#objc private func keyboardDidHide() {
didKeyboardShow = false
}
}

Related

Window hide doesn't deallocate memory

I have presented a custom UIWindow in order to show some screens. When I hide the window, the window instance doesn't free up. The underlying view controller also not getting released.
Custom window logic :
class SDKWindow: UIWindow {
// Unique tag to find the SDKWindow in the containers. Iterate all the windows and find the sdk window to show/hide any controller.
static let SDKWindowTag = 101011
init(rootController: UIViewController) {
if #available(iOS 13.0, *) {
let windowScene = UIApplication.shared
.connectedScenes
.filter { $0.activationState == .foregroundActive }
.first
if let windowScene = windowScene as? UIWindowScene {
super.init(windowScene: windowScene)
} else {
super.init(frame: UIScreen.main.bounds)
}
} else {
super.init(frame: UIScreen.main.bounds)
}
self.rootViewController = rootController
self.windowLevel = .statusBar - 1
self.tag = SDKWindow.SDKWindowTag
self.accessibilityLabel = "SDK.Window"
self.accessibilityViewIsModal = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func show() {
printLog("Show sdkwindow")
self.makeKeyAndVisible()
if self.isHidden == false {
return
}
self.isHidden = false
self.alpha = 1.0
}
func hide() {
if #available(iOS 13.0, *) {
self.windowScene = nil
} else {
self.rootViewController = nil
}
self.alpha = 0.0
self.isHidden = true
self.resignKey()
self.removeFromSuperview()
}
}
When the window presented is hidden
#objc public class MyClass: NSObject {
/// Private variables
private var popupWindow: SDKWindow?
public func showWindow() {
let viewController : CustomController = CustomController.init()
self.popupWindow = SDKWindow.init(rootController: viewController)
self.popupWindow?.show()
}
public func hide() {
self.popupWindow?.hide()
}
}
When hide method is called, CustomViewController Deinit is not called. But when
public func hide() {
self.popupWindow?.hide()
self.popupWindow = nil
}
is performed, the CustomViewController deinit is called and UIWindow instance is also deallocated in memory.

How to properly dispose of Connected Display Window and View?

I have code that opens a window and displays a view on a connected display. My goal is to detect a connection/disconnection of a connected display and show/remove the view accordingly. I have that part working fine.
The problem I am having is closing the window upon disconnection, but then if a subsequent connection is made, and upon creating the window and view again, I get a EXC_BAD_ACCESS error.
I tried a different approach by setting the connectedDisplayWindow and connectedDisplayView to nil, after calling close() on the window when a connected display is removed. Maybe I am misunderstanding the close() method?
Apple Documentation
If the window is set to be released when closed, a release message is sent to the object after the current event is completed. For an NSWindow object, the default is to be released on closing, while for an NSPanel object, the default is not to be released. You can use the isReleasedWhenClosed property to change the default behavior...
Just to make sure, I tried setting the isReleasedWhenClosed to true, but it did not change the problem.
The other thing I see in the console is about seven repeated error strings immediately upon disconnection of the connected display: 2022-04-10 10:28:11.044155-0500 External Display[95744:4934855] [default] invalid display identifier 67EE0C44-4E3D-3AF2-3447-A867F9FC477D before the notification is fired, and one more after the notification occurs: 2022-04-10 10:28:11.067555-0500 External Display[95744:4934855] [default] Invalid display 0x4e801884. Could these be related to the issues I am having?
Full example code:
ViewController.swift
import Cocoa
let observatory = NotificationCenter.default
class ViewController: NSViewController {
var connectedDisplay: NSScreen?
var connectedDisplayWindow: NSWindow?
var connectedDisplayView: NSView?
var connectedDisplayCount: Int = 0
var connectedDisplayID: UInt32 = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setupObservatory()
if NSScreen.screens.count > 1 {
handleDisplayConnectionChange(notification: nil)
}
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
override func viewWillDisappear() {
connectedDisplayWindow?.close()
}
func setupObservatory() {
observatory.addObserver(self, selector: #selector(handleDisplayConnectionChange), name: NSApplication.didChangeScreenParametersNotification, object: nil)
observatory.addObserver(forName: .setupConnectedDisplayWindow, object: nil, queue: nil, using: setupConnectedDisplayWindow)
}
#objc func handleDisplayConnectionChange(notification: Notification?) {
if connectedDisplayCount != NSScreen.screens.count {
if connectedDisplayCount < NSScreen.screens.count {
print("There is a connected display.")
connectedDisplayCount = NSScreen.screens.count
if let _ = NSScreen.screens.last {
if connectedDisplay != NSScreen.screens.last {
connectedDisplayID = NSScreen.screens.last!.displayID!
connectedDisplay = NSScreen.screens.last!
}
} else {
connectedDisplayID = 0
}
if connectedDisplayID != 0 && !connectedDisplayIsActive {
observatory.post(name: .setupConnectedDisplayWindow, object: nil)
}
} else if connectedDisplayCount > NSScreen.screens.count {
print("A connected display was removed.")
connectedDisplayCount = NSScreen.screens.count
connectedDisplayIsActive = false
connectedDisplayWindow?.close()
//connectedDisplayView = nil <- causes error #main in AppDelegate
//connectedDisplayWindow = nil <- causes error #main in AppDelegate
connectedDisplay = nil
connectedDisplayID = 0
}
}
}
func setupConnectedDisplayWindow(notification: Notification) {
if NSScreen.screens.count > 1 && !connectedDisplayIsActive {
connectedDisplay = NSScreen.screens.last
let mask: NSWindow.StyleMask = [.titled, .closable, .miniaturizable, .resizable]
connectedDisplayWindow = NSWindow(contentRect: connectedDisplay!.frame, styleMask: mask, backing: .buffered, defer: true, screen: connectedDisplay) // <- causes error on subsequent connection
connectedDisplayWindow?.level = .normal
connectedDisplayWindow?.isOpaque = false
connectedDisplayWindow?.backgroundColor = .clear
connectedDisplayWindow?.hidesOnDeactivate = false
let viewRect = NSRect(x: 0, y: 0, width: connectedDisplay!.frame.width, height: connectedDisplay!.frame.height)
connectedDisplayView = ConnectedDisplayView(frame: viewRect)
connectedDisplayWindow?.contentView = connectedDisplayView
connectedDisplayWindow?.orderFront(nil)
connectedDisplayView?.window?.toggleFullScreen(self)
connectedDisplayIsActive = true
observatory.post(name: .setupConnectedDisplayView, object: nil)
}
}
}
extension Notification.Name {
static var setupConnectedDisplayWindow: Notification.Name {
return .init(rawValue: "ViewController.setupConnectedDisplayView")
}
static var setupConnectedDisplayView: Notification.Name {
return .init(rawValue: "ConnectedDisplayView.setupConnectedDisplayView")
}
}
extension NSScreen {
var displayID: CGDirectDisplayID? {
return deviceDescription[NSDeviceDescriptionKey(rawValue: "NSScreenNumber")] as? CGDirectDisplayID
}
}
ConnectedDisplayView.swift
import Cocoa
var connectedDisplayIsActive: Bool = false
class ConnectedDisplayView: NSView {
var imageView: NSImageView!
override init(frame: NSRect) {
super.init(frame: frame)
setupObservatory()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupObservatory() {
observatory.addObserver(forName: .setupConnectedDisplayView, object: nil, queue: nil, using: setupConnectedDisplayView)
}
func setupConnectedDisplayView(notification: Notification) {
let imageURL = URL(fileURLWithPath: "/Users/Shared/my image.png")
if let image = NSImage(contentsOf: imageURL) {
imageView = NSImageView(image: image)
imageView.wantsLayer = true
imageView.frame = self.frame
imageView.alphaValue = 1
self.addSubview(imageView)
}
}
}
I commented out the nil settings for the connectedDisplayWindow and connectedDisplayView objects and the error at #main in AppDelegate went away, but then I get an error when trying to reinitialize the connectedDisplayWindow if the connected display is removed or the connection is momentarily interrupted.
The default value of isReleasedWhenClosed is true and connectedDisplayWindow?.close() releases the window. Setting connectedDisplayWindow to nil or to another window releases the window again and causes a crash. Solution: set isReleasedWhenClosed to false.

NScrollView in NSPopover autolayout issue

I have an NSPopover which contains an NSViewController with a containing NSScrollView.
The Popover height has to be either the height of the NSScrollView content or the current window. Once it hits the bounds of the window it should scroll.
Using Snapkit
I have added the NSScrollView to the controller:
view.addSubview(scrollView)
scrollView.snp.makeConstraints { (make) in
make.edges.equalTo(view)
make.height.equalTo(mainView.content.snp.height)
}
This works fine until the content is greater than the window, then what happens is the NSScrollView will not scroll to the top of the content because the view has pushed itself upwards out of bounds.
I have gone down the route of removing the height constraint and in the viewDidLayout try to update the height but it doesn't work.
If more code examples are needed let me know.
Finally got to the bottom of the issue and found a sensible solution.
The app I am developing has a few popovers that are required at various stages, to ensure that they closed as required I created a service that manages every popover, here is an example:
class PopoverService: NSObject {
enum PopoverType {
case subscription, edit
}
//================================================================================
// MARK: - Properties
//================================================================================
private var dismissingPopover = false
private lazy var currentPopover: NSPopover = {
let popover = NSPopover()
popover.delegate = self
return popover
}()
private var nextPopoverType: PopoverType?
private var currentView: NSView!
public static var delegate: PopoverServiceDelegate?
//================================================================================
// MARK: - Singleton
//================================================================================
static let shared = PopoverService()
//================================================================================
// MARK: - Helpers
//================================================================================
public static func increaseHeight(_ height: CGFloat) {
shared.currentPopover.contentSize.height = height
}
public static func isDisplayingType(_ type: PopoverType) -> Bool {
switch type {
case .edit:
return shared.currentPopover.contentViewController is EditEntryController
case .language:
return shared.currentPopover.contentViewController is CodeTypeController
default:
return false
}
}
public static func displayPopover(type: PopoverType, fromView view: NSView) {
shared.nextPopoverType = type
shared.currentView = view
switch type {
case .subscription:
displaySubscriptionPopoverFrom(view)
// Create functions to display your popovers
}
}
static func dismissPopover(clearUpcoming: Bool = true) {
if clearUpcoming {
shared.nextPopoverType = nil
}
shared.currentPopover.performClose(nil)
if shared.currentPopover.contentViewController == nil {
shared.dismissingPopover = false; return
}
}
}
extension PopoverService: NSPopoverDelegate {
func popoverDidClose(_ notification: Notification) {
currentPopover.contentViewController = nil
dismissingPopover = false
guard let nextPopoverType = nextPopoverType else { return }
PopoverService.displayPopover(
type: nextPopoverType,
fromView: currentView,
entry: currentEntry
)
}
}
To update the current popover, there is a function increaseHeight which takes and CGFloat and will update the current popovers height.
In the NSViewController override the viewDidLayout():
override func viewDidLayout() {
super.viewDidLayout()
let windowFrameHeight = view.window?.frame.size.height ?? 0
let contentHeight = scrollView.content.frame.height
let adjustment = contentHeight > windowFrameHeight ? windowFrameHeight : contentHeight
PopoverService.increaseHeight(adjustment)
if contentHeight > 0 && firstLayout {
if let documentView = scrollView.documentView {
documentView.scroll(NSPoint(x: 0, y: documentView.bounds.size.height))
}
}
}
The scrollView will need to be forced to the top so there is a variable firstLayout which you can set to true in the viewDidAppear

Find out when UIKeyboard.frame intersects with other frame?

I need to find out when the textfield becomes the first responder to notify me whether the keyboard that's going to show will obstruct the UITextField. If it does, I wanna adjust the scrollview properties.
So far I have this setup. I'm listening for UIKeyboardWillShow notifications that calls the following selector:
func keyboardWillAppear(notification:NSNotification)
{
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
{
if keyboardSize.intersects(textField.frame)
{
print("It intersects")
}
else
{
print("Houston, we have a problem")
}
}
Note: I tried with UIKeyboardDidShow but still no success. UITextField is a subview of the scrollView.
listen to size changes of the keyboard
CONVERT the coordinates
working sample:
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
//keyboard observers
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChange), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
func keyboardWillChange(notification:NSNotification)
{
print("Keyboard size changed")
if let keyboardSize = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? CGRect {
//convert gotten rect
let r = self.view.convert(keyboardSize, from: nil)
//test it
if r.intersects(textView.frame) {
print("intersects!!!")
}
}
}
How about comparing the start position of the keyboard with the end position of the text?
working sample:
func keyboardWillAppear(notification:NSNotification)
{
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
{
if keyboardSize.origin.y < textField.frame.origin.y + textField.frame.size.height {
print("It intersects")
} else {
print("Houston, we have a problem")
}
}
}

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
}