I am trying to create a custom UIToolbar that I can add to multiple views, I don't want to do this programmatically so I am using xib to design a UIToolbar. Do I need to wrap this in a UIView and a View Controller? ViewController.m is the root view controller. This is just a simple project I am just trying out some stuff
You can do this so:
create ToolBar.xib file, delete UIView and add UIToolbar
create ToolBar.swift file, add code, like shown, and make outlets
for .xib in "Identity and Type" put Name "ToolBar.swift"
In your root View Controller put this code in ViewDidLoad:
let toolBar = UINib(nibName: "ToolBar", bundle: nil).instantiateWithOwner(nil, options: nil).first as! ToolBar
toolBar.hidden = true
self.navigationController!.view.addSubview(toolBar)
self.navigationController!.toolbarItems = toolBar.items
In all View Controllers put in ViewDidLoad:
self.toolbarItems = self.navigationController!.toolbarItems
ToolBar.swift:
import UIKit
class ToolBar: UIToolbar {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
#IBAction func item1Pressed(sender: AnyObject) {
print("item 1 pressed")
}
#IBAction func item2Pressed(sender: AnyObject) {
print("item 2 pressed")
}
#IBAction func item3Pressed(sender: AnyObject) {
print("item 3 pressed")
}
#IBAction func item4Pressed(sender: AnyObject) {
print("item 4 pressed")
}
}
Related
I added a custom view, that has a button, to my viewController. I want to make an #IBAction from that button to my viewController.
I have following problems:
How do I make it show up in a storyboard? When I run the app it works fine.
How do I make an #IBAction from that button to my viewController?
class CustomCellView: UIView{
//MARK: Initializer
override func awakeFromNib() {
super.awakeFromNib()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
commonInit()
}
//MARK: Functions
func commonInit(){
let customView = Bundle.main.loadNibNamed("customCellView", owner: self, options: nil)?.first as? UIView ?? UIView()
customView.frame = self.bounds
self.addSubview(customView)
}
}
Add #objc in your viewController where you added customView.
e.g.
#objc func actionCustom()
{
print("Click Added")
}
Use
func commonInit(){
let customView = Bundle.main.loadNibNamed("customCellView", owner: self, options: nil)?.first as? UIView ?? UIView()
customView.frame = self.bounds
self.addSubview(customView)
customView.button.addTarget(self, action: #selector(actionCustom), for: .touchUpInside) //Add click code here
}
my customView is not showing in storyboard but when I run the app it's ok
This is the expected behaviour with the custom views. You won't get to see how the view looks in the xib that you use the custom view only in the xib that you defined the view.
How make #IBAction from that button to my viewController.
You can't. Custom views don't make public the subviews in interface builder. The best thing you can do is to use the delegate pattern or as Bhavesh Sarsawa pointed out doing it programatically either by adding the vc as a target or by making an IBAction in the ccustom view which calls a closure that is sent by dependency injection.
customButton.setAction { code that gets called when the button is pressed }
I want to create a reusable button all over my app and was planning to design it with it's own .xib file. The issue is that I can't connect an IBAction to the custom button in the controllers where it's used.
I created a new .xib file called SampleButton.xib and added a button. This is what the hierarchy and the view looks like:
I then created a new swift file called SampleButton.swift with a class called SampleButton that's a subclass of UIButton and assigned it as the File's Owner in my SampleButton.xib file.
The contents of SampleButton.swift are as follows:
import Foundation
import UIKit
#IBDesignable
class SampleButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
guard let view = loadViewFromNib() as? UIButton else {
return
}
view.frame = bounds
view.autoresizingMask = [UIView.AutoresizingMask.flexibleWidth,
UIView.AutoresizingMask.flexibleHeight]
addSubview(view)
view.layer.borderWidth = 2
view.layer.borderColor = UIColor.white.cgColor
}
func loadViewFromNib() -> UIView? {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as? UIButton
}
#IBAction func pressed(_ sender: Any) {
print("Called in here")
}
}
I can then create a new button in my storyboard and set it to custom and the class to SampleButton. However now if I ctrl + drag from my button to my corresponding View Controller to create an IBAction for the button, it's not called. The one in the SampleButton.swift file is. Even if I delete the IBAction in the SampleButton file it's still not called.
Any help here? I want to be able to design the buttons separately and then have IBactions for them in the controllers where they're used.
I encountered this same issue with some of my custom xib views and my initial thought was that I could set up my xib to be IBDesignable and then connect outlets from the storyboard rendering of my button in the view controller.
That didn't work.
So I setup a bit of a workaround using delegate callbacks from my custom views. I created IBOutlets for the view to the view controllers using them, then in viewDidLoad I'd set the delegate and handle the button tap in the view controller
import UIKit
// defines a callback protocol for the SampleButtonView
protocol SampleButtonViewDelegate: class {
func sampleButtonTapped(_ button: SampleButton)
}
#IBDesignable
class SampleButton: UIView, NibLoadable {
// create IBOutlet to button if you want to register a target/action directly
#IBOutlet var button: UIButton!
// set delegate if you want to handle button taps via delegate
weak var delegate: SampleButtonViewDelegate?
// initializers to make it so this class renders in view controllers
// when using IBDesignable
convenience init() {
self.init(frame: .zero)
}
override init(frame: CGRect) {
super.init(frame: frame)
loadFromNib(owner: self)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
loadFromNib(owner: self)
}
#IBAction func buttonTapped(_ sender: Any) {
delegate?.sampleButtonTapped(_ button: self)
}
}
// here's a sample ViewController using this view and the delegate callback method
class ViewController: UIViewController {
#IBOutlet var sampleButtonView: SampleButton!
override func viewDidLoad() {
super.viewDidLoad()
sampleButtonView.delegate = self
}
}
extension ViewController: SampleButtonViewDelegate {
func sampleButtonTapped(_ button: SampleButton) {
// TODO: run logic for button tap here
}
}
For completeness I'll also add this NibLoadable protocol definition here.
// I used this for the #IBDesignable functionality to work and actually render
// my xib layouts in the storyboard view controller layouts using this class
import UIKit
/// Defines an interface for UIViews defined in .xib files.
public protocol NibLoadable {
// the name of the associated nib file
static var nibName: String { get }
// loads the view from the nib
func loadFromNib(owner: Any?)
}
public extension NibLoadable where Self: UIView {
/// Specifies the name of the associated .xib file.
/// Defaults to the name of the class implementing this protocol.
/// Provide an override in your custom class if your .xib file has a different name than it's associated class.
static var nibName: String {
return String(describing: Self.self)
}
/// Provides an instance of the UINib for the conforming class.
/// Uses the bundle for the conforming class and generates the UINib using the name of the .xib file specified in the nibName property.
static var nib: UINib {
let bundle = Bundle(for: Self.self)
return UINib(nibName: Self.nibName, bundle: bundle)
}
/// Tries to instantiate the UIView class from the .xib file associated with the UIView subclass conforming to this protocol using the owner specified in the function call.
/// The xib views frame is set to the size of the parent classes view and constraints are set to make the xib view the same size as the parent view. The loaded xib view is then added as a subview.
/// This should be called from the UIView's initializers "init(frame: CGRect)" for instantiation in code, and "init?(coder aDecoder: NSCoder)" for use in storyboards.
///
/// - Parameter owner: The file owner. Is usually an instance of the class associated with the .xib.
func loadFromNib(owner: Any? = nil) {
guard let view = Self.nib.instantiate(withOwner: owner, options: nil).first as? UIView else {
fatalError("Error loading \(Self.nibName) from nib")
}
view.frame = self.bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(view)
}
}
You could also simply register the functions you defined in your view controller as the target/action functions for the button in the custom view.
override func viewDidLoad() {
super.viewDidLoad()
mySampleButtonView.button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
}
#objc func buttonTapped(_ sender: UIButton) {
// handle button tap action in view controller here...
}
create iboutlet of button in nib class.
add you nib view in your viewcontroller where its needed.
add target for the button outlet.
try following code:
override func viewDidLoad() {
super.viewDidLoad()
let myButton = Bundle.main.loadNibNamed("myButtonxibName", owner: self, options: nil)?[0] as? myButtonxibClassName
myButton.button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
self.view.addsubview(myButton)
}
#objc func buttonTapped() {}
You don't need a Xib for what you're trying to do. Remove the loadViewFromNib() and the pressed(_ sender: Any) functions from your class above. Change your setup() method to customize your button. I see that you want to add a border to it. Do something like this:
func setup() {
self.layer.borderWidth = 2
self.layer.borderColor = UIColor.white.cgColor
// * Any other UI customization you want to do can be done here * //
}
In your storyboard, drag and drop a regular UIButton wherever you want to use it, set the class in the attributes inspector to SampleButton, connect your IBOutlet and IBActions as necessary, and it should be good to go.
I don't think it's possible to do this. Simpler way is to just set the target and action in view controllers. Something like:
class VC: UIViewController {
func viewDidLoad() {
sampleButton.addTarget(self, action: #selector(didClickOnSampleButton))
}
}
I have created a custom view xib and give that view class. Now I take a view in main vc and give that class but now I want to access custom view button action method in my main vc. So how can I do that?
Here is my custom view
import UIKit
class TextCustomisationVC: UIView {
#IBOutlet var contentView: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonInit()
}
private func commonInit(){
Bundle.main.loadNibNamed("TextCustomisationVC", owner: self, options: nil)
addSubview(contentView)
contentView.frame = self.bounds
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
#IBAction func btnCloseCustomisation_Click(_ sender: Any) {
}
#IBAction func btnApplyCustomisation_Click(_ sender: Any) {
}
}
Now I create an outlet in my main VC and give that same class I can access those class outlets but now I want to access above button action method So how can I do that?
You can use delegate here which you can implement in the main VC.
create a protocol like this:
protocol ButtonActionDelegate {
func closeButtonPressed(_ sender:UIButton)
func applyButtonPressed(_ sender:UIButton)
}
Then create instance of the delegate in your view like this:
var delegate:ButtonActionDelegate?
Implement this delegate in the main VC like this:
extension mainVC : ButtonActionDelegate {
func closeButtonPressed(_ sender: UIButton) {
}
func applyButtonPressed(_ sender: UIButton) {
}
}
Then you can call the delegate methods respectively like this:
#IBAction func btnCloseCustomisation_Click(_ sender: Any) {
self.delegate?.closeButtonPressed(sender)
}
#IBAction func btnApplyCustomisation_Click(_ sender: Any) {
self.delegate?.applyButtonPressed(sender)
}
You can try
let cusView = TextCustomisationVC(frame:///)
if btn sender is used inside function
cusView.btnCloseCustomisation_Click(cusView.closeBtn)
otherwise send any dummy button
cusView.btnCloseCustomisation_Click(UIButton())
Edit:
protocol CustomTeller {
func closeClicked(UIButton)
}
class TextCustomisationVC: UIView {
var delegate: CustomTeller?
#IBAction func btnCloseCustomisation_Click(_ sender: UIButton) {
self.delegate?.closeClicked(sender:sender)
}
}
// in mainVC
let cusView = TextCustomisationVC(frame:///)
cusView.delegate = self
and implement
func closeClicked(sender:UIButton) {
// close button called
}
Ive been searching for a while for a simple example on how to reuse views from xib files in my storyboard but all i find was outdated or dosen't solve my problem
the situation is that I has simple:
I have a viewController in my storyboard
I dragged to it two view from the library
I had created a myCustomView.xib and myCustomView.swift file (they are empty now)
I have I button on viewController (so the tree (two view and one button) are setting together on the viewController in the storyboard)
the question is: I want one view to be loaded dynamically on app launch and the other one to be loaded on button click
an other question: how can I connect myCustomView to that viewController
thank you
I've implemented an extension for UIView:
extension UIView {
static func createInstance<T: UIView>(ofType type: T.Type) -> T {
let className = NSStringFromClass(type).components(separatedBy: ".").last
return Bundle.main.loadNibNamed(className!, owner: self, options: nil)![0] as! T
}
}
In this way, wherever you can load your custom view in this way:
func override viewDidLoad() {
super.viewDidLoad()
let customView = UIView.createInstance(ofType: CustomView.self)
self.view.addSubview(customView)
}
Add bellow code in your custom view class
class MyCustomView: UIView {
#IBOutlet var contentView: UIView! // take view outlet
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
func xibSetup() {
contentView = loadViewFromNib()
// use bounds not frame or it'll be offset
contentView!.frame = bounds
//Make the view stretch with containing view
contentView!.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]
// Adding custom subview on top of our view (over any custom drawing > see note below)
addSubview(contentView!)
layoutIfNeeded()
}
override func layoutIfNeeded() {
super.layoutIfNeeded()
print("layoutIfNeeded")
}
func loadViewFromNib() -> 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)[0] as! UIView
return view
}
}
Add this class as superclass view in storyboard.
I made a subview appear on my iOS app when a button is pressed. Then, I call a custom UIView from its class, menuView, and display that UIView on my main storyboard
The menuView appears fine on my Main Storyboard, but once I remove the subview, my SuperView (original view for main class for main view controller) is unresponsive, and I can't interact with anything.
What's wrong?
My Custom "menuView" UIView Class:
import UIKit
#IBDesignable class menuView: UIView {
var view:UIView!
#IBOutlet weak var label: UILabel!
func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "menuView", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
func xibSetup() {
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
addSubview(view)
}
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
xibSetup()
}
}
Main View Controller Class:
var myMenuView:menuView!
#IBOutlet weak var menuButtonOutlet: UIBarButtonItem!
#IBAction func buttonPressed(sender: AnyObject) {
if menuButtonOutlet.title == "Menu" {
if (myMenuView != nil) {
self.myMenuView.view.removeFromSuperview()
}
self.myMenuView = menuView(frame: self.view.bounds)
self.myMenuView.alpha = 0.75
self.view.addSubview(myMenuView)
menuButtonOutlet.title = "Back"
} else {
self.myMenuView.view.removeFromSuperview()
menuButtonOutlet.title = "Menu"
}
}
Short answer:
self.myMenuView.view.removeFromSuperview()
should become
self.myMenuView.removeFromSuperview()
in both places you have this line in buttonPressed.
Explanation:
Your myMenuView is a container in which you place the view you instantiate from your xib. In buttonPressed you remove only this inner(xib) view inside myMenuView and not myMenuView itself. Thus myMenuView remains on screen and swallows all the touches.