Code for the Custom UIView:
Please check the video too here: https://drive.google.com/open?id=1kbrOxXWcJIi4vkiqMNer3exBr5cOWgDz
import UIKit
protocol PostAttachmentFullScreenViewDelegate: class {
func closeAttachmentFullView()
}
class PostAttachmentFullScreenView: UIView {
weak var delegate: PostAttachmentFullScreenViewDelegate?
#IBOutlet var backgroundView: UIImageView!
#IBOutlet var closeButton: UIButton!
#IBAction func closeViewAction(_ sender: Any) {
print("will call delegate to put it off")
self.delegate?.closeAttachmentFullView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let _ = commonInitialization()
backgroundView.image = UIImage(named: "ScrollImageTop1")
closeButton.isUserInteractionEnabled = true
}
override init(frame: CGRect) {
super.init(frame: frame)
let _ = commonInitialization()
backgroundView.image = UIImage(named: "ScrollImageTop1")
closeButton.isUserInteractionEnabled = true
}
func commonInitialization() -> UIView
{
let bundle = Bundle.init(for: type(of: self))
let nib = UINib(nibName: "PostAttachmentFullScreenView", bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
view.frame = bounds
view.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]
addSubview(view)
return view
}
}
usage in ViewController (I am defining an instance of the custom view and putting it inside the Scroll View):
var frame = CGRect(x:0, y:0, width:0, height:0)
let blue = PostAttachmentFullScreenView()
blue.delegate = self
blue.isUserInteractionEnabled = true
blue.backgroundColor = UIColor.blue
blue.backgroundView.image = fileAttachments[1]
frame.origin.x = attachmentsScrollView.frame.size.width * CGFloat (0)
frame.size = attachmentsScrollView.frame.size
blue.frame = frame
attachmentsScrollView.addSubview(blue)
extension NewPostViewController : PostAttachmentFullScreenViewDelegate
{
func closeAttachmentFullView() {
print("hiding attachments view")
attachmentSuperView.isHidden = true
}
}
To my surprise it doesn't even print - "will call delegate to put it off".
I am not able to understand what's wrong here. Please help me understand the issue and correct it. Thank you.
You are mixing programmatic approach and xib approach.
As you have added IBOultet and IBAction that means you are using xib for the UIView.
In that scenario you have to load the UIView xib when initialising the view.
Add an extension for UIView in your project:
extension UIView {
class func fromNib<T: UIView>() -> T {
return Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T
}
}
when you are initialising your view add it like this :
let blue : PostAttachmentFullScreenView = UIView.fromNib()
blue.delegate = self
blue.isUserInteractionEnabled = true
blue.backgroundColor = UIColor.blue
blue.backgroundView.image = fileAttachments[1]
frame.origin.x = attachmentsScrollView.frame.size.width * CGFloat (0)
frame.size = attachmentsScrollView.frame.size
blue.frame = frame
attachmentsScrollView.addSubview(blue)
and the delegate and button action methods will work.
you missed this :
You never set the target/action on your button. Somewhere you need to call addTarget(_:action:for:) to set the target/action on the button. Also, what connects the button to your PostAttachmentFullScreenView as an outlet?
This might be an obvious one but for me (Xcode 10.1) adding all missing UI constraints to the UIButton in question (at least 4 constraints) fixed the error for me in my custom view:
Make sure you add enough constraints (typically 4 constraints) or enough to have all warnings regarding missing constraints removed. After doing this and attaching the button with Ctrl + drag from View to corresponding swift code, the click was being detected and working properly.
Hope this helps.
I just got finished going through the big nerd ranch iOS programming book and started my first 'project'. I'm trying to use a UIImageView as a button but my image is coming up as nil and I cannot figure out why. I'm new at this, so any help, or even just identifying any parts of this code that don't make sense is appreciated.
import UIKit
class ImageScreenViewController: UIViewController {
# IBOutlet var imageView: UIImageView!
// set up images
let picture1 = UIImage(named: "picture1")
override func viewDidLoad() {
super.viewDidLoad()
// set image on screen
imageView.image = picture1
}
// set up gesture recognizer
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(count(_:)))
self.imageView.addGestureRecognizer(tapRecognizer)
self.imageView.isUserInteractionEnabled = true
}
#objc func count(_ gestureRecognizer: UIGestureRecognizer) {
print("tapped the picture")
}
}
note, picture1 is in Assets.xcassets.
Some explanation for your question
encodeWithCoder(_ aCoder: NSCoder) {
// Serialize
}
init(coder aDecoder: NSCoder) {
// Deserialize
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//You used
}
To add a Gesture you actually do not need to implement coder here as These are being used to store the state
Check following example
1- ImageView is being taken from storyBoard
2- myImage is being Added using code
Now , Storyboard initially implants serialise and deserialise mechanism
Thus imageView is a property being added from storyboard so no need for coder there
import UIKit
class ImageVC: UIViewController {
# IBOutlet var imageView: UIImageView!
// set up images
let picture1 = UIImage(named: "cc")
//ImageView programmaticlly
var myImage = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// set image on screen
imageView.image = picture1
//setting properties and frame
myImage.image = picture1
myImage.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
self.view.addSubview(myImage)
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(count(_:)))
self.imageView.addGestureRecognizer(tapRecognizer)
self.imageView.isUserInteractionEnabled = true
}
// set up gesture recognizer
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//adding gesture
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(count(_:)))
self.myImage.addGestureRecognizer(tapRecognizer)
self.myImage.isUserInteractionEnabled = true
}
#objc func count(_ gestureRecognizer: UIGestureRecognizer) {
print("tapped the picture")
}
}
As if you implement above Algo It won't crash , as For Properties being added from storyboard can directly be accessed in didload or willAppear as we never going to remove them from superview so indirectly its serialised
Second case - myImage - Added Programmatically can also be removed from superView and added again Thus can be serialised means need to be
you can add tapRecongnize in viewDidload because in init method imageview not initialize and return nil so that you can use like this
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = picture1
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(count(_:)))
self.imageView.addGestureRecognizer(tapRecognizer)
self.imageView.isUserInteractionEnabled = true
}
var image: UIImage?
#IBOutlet weak var imageChoisie: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
imageChoisie.image = image // from Segue
// Click on Image
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(count(_:)))
self.imageChoisie.addGestureRecognizer(tapRecognizer)
self.imageChoisie.isUserInteractionEnabled = true
}
#objc func count(_ gestureRecognizer: UIGestureRecognizer) {
//print("tapped the picture")
dismiss(animated: true, completion: nil) // back previous screen
}
I have a default UIButton linked to an IBAction (touch up inside). This works fine, so I know my connection is OK. Once I change the UIButton class to my custom button, the IBAction is no longer triggered. Below is the code for my custom UIButton. Any ideas why this is happening?
import UIKit
#IBDesignable
class PrimaryButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
initView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initView()
}
private func initView() {
let view = viewFromNibForClass()
view.frame = bounds
view.autoresizingMask = [
UIViewAutoresizing.flexibleWidth,
UIViewAutoresizing.flexibleHeight
]
addSubview(view)
}
private func viewFromNibForClass() -> UIView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil).first as! UIView
return view
}
}
Your code does not work because a button does not receive touches anymore. All touches are sent to the topmost custom view you add.
Set view.userInteractionEnabled = false to make it transparent to touches.
Your custom button is covered by the view you added.
I'm using Swift 3, I have error adding xib file as subview
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "CustomView", bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.addSubview(view);
Can someone help me please?
In my Custom View Class i implemented as follow:
import UIKit
#IBDesignable class TestView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
loadViewFromNib()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func loadViewFromNib(){
let view = Bundle.main.loadNibNamed("test", owner: self, options: nil)?.first as! UIView
print(view.backgroundColor ?? UIColor.blue)
}
}
then In my ViewController where i want add this custom view i write as below:
let view = TestView()
and i dont find any crash.
Try this, it is for swift 3.
let view = Bundle.main.loadNibNamed("CustomView", owner: self, options: nil)?.first as! UIView
self .addSubview(view)
I have a class called MyClass which is a subclass of UIView, that I want to initialize with a XIB file. I am not sure how to initialize this class with the xib file called View.xib
class MyClass: UIView {
// what should I do here?
//init(coder aDecoder: NSCoder) {} ??
}
I tested this code and it works great:
class MyClass: UIView {
class func instanceFromNib() -> UIView {
return UINib(nibName: "nib file name", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as UIView
}
}
Initialise the view and use it like below:
var view = MyClass.instanceFromNib()
self.view.addSubview(view)
OR
var view = MyClass.instanceFromNib
self.view.addSubview(view())
UPDATE Swift >=3.x & Swift >=4.x
class func instanceFromNib() -> UIView {
return UINib(nibName: "nib file name", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
}
Sam's solution is already great, despite it doesn't take different bundles into account (NSBundle:forClass comes to the rescue) and requires manual loading, a.k.a typing code.
If you want full support for your Xib Outlets, different Bundles (use in frameworks!) and get a nice preview in Storyboard try this:
// NibLoadingView.swift
import UIKit
/* Usage:
- Subclass your UIView from NibLoadView to automatically load an Xib with the same name as your class
- Set the class name to File's Owner in the Xib file
*/
#IBDesignable
class NibLoadingView: UIView {
#IBOutlet weak var view: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
nibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
nibSetup()
}
private func nibSetup() {
backgroundColor = .clearColor()
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
view.translatesAutoresizingMaskIntoConstraints = true
addSubview(view)
}
private func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: String(self.dynamicType), bundle: bundle)
let nibView = nib.instantiateWithOwner(self, options: nil).first as! UIView
return nibView
}
}
Use your xib as usual, i.e. connect Outlets to File Owner and set File Owner class to your own class.
Usage: Just subclass your own View class from NibLoadingView & Set the class name to File's Owner in the Xib file
No additional code required anymore.
Credits where credit's due: Forked this with minor changes from DenHeadless on GH. My Gist: https://gist.github.com/winkelsdorf/16c481f274134718946328b6e2c9a4d8
As of Swift 2.0, you can add a protocol extension. In my opinion, this is a better approach because the return type is Self rather than UIView, so the caller doesn't need to cast to the view class.
import UIKit
protocol UIViewLoading {}
extension UIView : UIViewLoading {}
extension UIViewLoading where Self : UIView {
// note that this method returns an instance of type `Self`, rather than UIView
static func loadFromNib() -> Self {
let nibName = "\(self)".characters.split{$0 == "."}.map(String.init).last!
let nib = UINib(nibName: nibName, bundle: nil)
return nib.instantiateWithOwner(self, options: nil).first as! Self
}
}
And this is the answer of Frederik on Swift 3.0
/*
Usage:
- make your CustomeView class and inherit from this one
- in your Xib file make the file owner is your CustomeView class
- *Important* the root view in your Xib file must be of type UIView
- link all outlets to the file owner
*/
#IBDesignable
class NibLoadingView: UIView {
#IBOutlet weak var view: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
nibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
nibSetup()
}
private func nibSetup() {
backgroundColor = .clear
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.translatesAutoresizingMaskIntoConstraints = true
addSubview(view)
}
private func loadViewFromNib() -> UIView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
let nibView = nib.instantiate(withOwner: self, options: nil).first as! UIView
return nibView
}
}
Universal way of loading view from xib:
Example:
let myView = Bundle.loadView(fromNib: "MyView", withType: MyView.self)
Implementation:
extension Bundle {
static func loadView<T>(fromNib name: String, withType type: T.Type) -> T {
if let view = Bundle.main.loadNibNamed(name, owner: nil, options: nil)?.first as? T {
return view
}
fatalError("Could not load view with type " + String(describing: type))
}
}
Swift 4
Here in my case I have to pass data into that custom view, so I create static function to instantiate the view.
Create UIView extension
extension UIView {
class func initFromNib<T: UIView>() -> T {
return Bundle.main.loadNibNamed(String(describing: self), owner: nil, options: nil)?[0] as! T
}
}
Create MyCustomView
class MyCustomView: UIView {
#IBOutlet weak var messageLabel: UILabel!
static func instantiate(message: String) -> MyCustomView {
let view: MyCustomView = initFromNib()
view.messageLabel.text = message
return view
}
}
Set custom class to MyCustomView in .xib file. Connect outlet if necessary as usual.
Instantiate view
let view = MyCustomView.instantiate(message: "Hello World.")
Swift 3 Answer: In my case, I wanted to have an outlet in my custom class that I could modify:
class MyClassView: UIView {
#IBOutlet weak var myLabel: UILabel!
class func createMyClassView() -> MyClassView {
let myClassNib = UINib(nibName: "MyClass", bundle: nil)
return myClassNib.instantiate(withOwner: nil, options: nil)[0] as! MyClassView
}
}
When in the .xib file, make sure that the Custom Class field is MyClassView. Don't bother with the File's Owner.
Also, make sure that you connect the outlet in MyClassView to the label:
To instantiate it:
let myClassView = MyClassView.createMyClassView()
myClassView.myLabel.text = "Hello World!"
Swift 5.3
Create a class named NibLoadingView with the following contents:
import UIKit
/* Usage:
- Subclass your UIView from NibLoadView to automatically load an Xib with the same name as your class
- Set the class name to File's Owner in the Xib file
*/
#IBDesignable
class NibLoadingView: UIView {
#IBOutlet public weak var view: UIView!
private var didLoad: Bool = false
public override init(frame: CGRect) {
super.init(frame: frame)
self.nibSetup()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.nibSetup()
}
open override func layoutSubviews() {
super.layoutSubviews()
if !self.didLoad {
self.didLoad = true
self.viewDidLoad()
}
}
open func viewDidLoad() {
self.setupUI()
}
private func nibSetup() {
self.view = self.loadViewFromNib()
self.view.frame = bounds
self.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.view.translatesAutoresizingMaskIntoConstraints = true
addSubview(self.view)
}
private func loadViewFromNib() -> UIView {
guard let nibName = type(of: self).description().components(separatedBy: ".").last else {
fatalError("Bad nib name")
}
if let defaultBundleView = UINib(nibName: nibName, bundle: Bundle(for: type(of: self))).instantiate(withOwner: self, options: nil).first as? UIView {
return defaultBundleView
} else {
fatalError("Cannot load view from bundle")
}
}
}
Now create a XIB & UIView class pair, set XIB's owner to UIView class and subclass NibLoadingView.
You can now init the class just like ExampleView(), ExampleView(frame: CGRect), etc or directly from storyboards.
You can also use viewDidLoad just like in UIViewController. All your outlets and layouts are rendered in that moment.
Based on Frederik's answer
Below code will do the job if anyone wants to load a custom View with XIB Programmatically.
let customView = UINib(nibName:"CustomView",bundle:.main).instantiate(withOwner: nil, options: nil).first as! UIView
customView.frame = self.view.bounds
self.view.addSubview(customView)
create view from .xib
let nib = UINib(nibName: "View1", bundle: nil) //View1 is a file name(View1.swift)
if let view = nib.instantiate(withOwner: self, options: nil).first as? UIView {
// logic
}
//or
if let view = Bundle.main.loadNibNamed("View1", owner: self, options: nil)?.first as? UIView {
// logic
}
Since .xib can contains several view, that is why you are working with array here(.first)
For example
Create View1.xib
Create View1.swift where set owner(loadNibNamed()) in code to create the instance of class("View1")
Set File's Owner in View1.xib as View1. Allows to connect outlets and actions
import UIKit
class View1: UIView {
#IBOutlet var contentView: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.commonInit()
}
private func commonInit() {
if let view = Bundle.main.loadNibNamed("View1", owner: self, options: nil)?.first as? UIView {
addSubview(view)
view.frame = self.bounds
}
}
}
Notes
if we move Custom Class from File's owner to Container View we get error(loop). It is because of:
System init instance from Container View where we init it again in commonInit()
.loadNibNamed
Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ff7bf6fbfc8)
override func draw(_ rect: CGRect)
{
AlertView.layer.cornerRadius = 4
AlertView.clipsToBounds = true
btnOk.layer.cornerRadius = 4
btnOk.clipsToBounds = true
}
class func instanceFromNib() -> LAAlertView {
return UINib(nibName: "LAAlertView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! LAAlertView
}
#IBAction func okBtnDidClicked(_ sender: Any) {
removeAlertViewFromWindow()
UIView.animate(withDuration: 0.4, delay: 0.0, options: .allowAnimatedContent, animations: {() -> Void in
self.AlertView.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)
}, completion: {(finished: Bool) -> Void in
self.AlertView.transform = CGAffineTransform.identity
self.AlertView.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)
self.AlertView.isHidden = true
self.AlertView.alpha = 0.0
self.alpha = 0.5
})
}
func removeAlertViewFromWindow()
{
for subview in (appDel.window?.subviews)! {
if subview.tag == 500500{
subview.removeFromSuperview()
}
}
}
public func openAlertView(title:String , string : String ){
lblTital.text = title
txtView.text = string
self.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
appDel.window!.addSubview(self)
AlertView.alpha = 1.0
AlertView.isHidden = false
UIView.animate(withDuration: 0.2, animations: {() -> Void in
self.alpha = 1.0
})
AlertView.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)
UIView.animate(withDuration: 0.3, delay: 0.2, options: .allowAnimatedContent, animations: {() -> Void in
self.AlertView.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)
}, completion: {(finished: Bool) -> Void in
UIView.animate(withDuration: 0.2, animations: {() -> Void in
self.AlertView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
})
})
}