Swift 3: InputAccessoryView instantiated from .xib file - ios

I'm having a problem with instantiating my InputAccessoryView from a .xib file, where the inputAccessoryView doesn't react on input:
private var conversationToolBar = ConversationToolBar()
override var inputAccessoryView: UIView? {
return self.conversationToolBar
}
If I instantiate my inputAccessoryView from a method (createAccessoryView):
private var conversationToolBar = ConversationToolBar().createAccessoryView(width: UIScreen.main.bounds.width)
My class:
class ConversationToolBar: UIView {
#IBOutlet var view: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
self.view = UINib(nibName: "ConversationToolBar", bundle: nil).instantiate(withOwner: self, options: nil).first as! UIView
self.addSubview(self.view)
self.view.frame = self.bounds
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.view = UINib(nibName: "ConversationToolBar", bundle: nil).instantiate(withOwner: self, options: nil).first as! UIView
self.addSubview(self.view)
self.view.frame = self.bounds
}
func createAccessoryView(width: CGFloat) -> UIView {
let accessoryView = UIView(frame: CGRect(x: 0, y: 0, width: width, height: 100))
accessoryView.backgroundColor = UIColor.blue
let closeLabel = UITextField(frame: accessoryView.frame)
closeLabel.font = UIFont.boldSystemFont(ofSize: 14)
closeLabel.text = "Hello"
closeLabel.textColor = UIColor.white
closeLabel.textAlignment = .center
accessoryView.addSubview(closeLabel)
return accessoryView
}
}
The behaviour works as expected.
Can anyone help me? Thank you

I had same issue with showing inputAccessoryView as expected , and fixed it with calling this method to refresh input or accessory view :
closeLabel.reloadInputViews()
Updates the custom input and accessory views when the object is the
first responder. You can use this method to refresh the custom input
view or input accessory view associated with the current object when
it is the first responder. The views are replaced immediately—that is,
without animating them into place. If the current object is not the
first responder, this method has no effect.

Related

How to make a button work on a custom view from .xib instantiated programmatically

The button works if I add the view via the Interface Builder but it doesn't work when I add the view programmatically.
.xib design:
My custom view class:
class customView: UIView {
static let singleton1 = customView()
#IBOutlet weak var newView: UIView!
#IBAction func changeColor(_ sender: Any) {
newView.backgroundColor = UIColor.systemBlue // this is what the button should do
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
window?.windowLevel = UIWindow.Level(rawValue: CGFloat.greatestFiniteMagnitude)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
window?.windowLevel = UIWindow.Level(rawValue: CGFloat.greatestFiniteMagnitude)
}
func commonInit() {
let viewFromXib = Bundle.main.loadNibNamed("customView", owner: self, options: nil)![0] as! UIView
viewFromXib.bounds = self.bounds
addSubview(viewFromXib)
}
This is the way I instantiate it:
#IBAction func showView(_ sender: Any) {
let playerview = customView.singleton1
view.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(playerview)
playerview.tag = 100
UIApplication.shared.keyWindow?.addSubview(playerview)// yes it needs to be in the window
Not sure if my initializer is wrong or something else.
You are trying to load "customView" xib into its own init method. Try below.
Replace commonInit() method with getView(frame:CGRect)->UIView
class customView: UIView {
static let singleton1 = customView()
#IBOutlet weak var newView: UIView!
#IBAction func changeColor(_ sender: Any) {
newView.backgroundColor = UIColor.systemBlue // this is what the button should do
}
override init(frame: CGRect) {
super.init(frame: frame)
window?.windowLevel = UIWindow.Level(rawValue: CGFloat.greatestFiniteMagnitude)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
window?.windowLevel = UIWindow.Level(rawValue: CGFloat.greatestFiniteMagnitude)
}
func getView(frame:CGRect)->UIView{
let viewFromXib = Bundle.main.loadNibNamed("customView", owner: nil, options: nil)![0] as! UIView
viewFromXib.frame = frame
return viewFromXib
}
}
Replace showView() method
#IBAction func showView(_ sender: Any) {
let playerview = customView.singleton1.getView(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 200))
self.view.addSubview(playerview)
playerview.tag = 100
UIApplication.shared.keyWindow?.addSubview(playerview)
}

How to set inputViewStyle on UIInputView (initialized in XIB)?

I'm implementing a view above the keyboard with some additional buttons.
I saw that you can use UIInputView like this:
let keyboardAccessoryView = UIInputView(frame: .init(x: 0, y: 0, width: 0, height: 40), inputViewStyle: .keyboard)
textView.inputAccessoryView = keyboardAccessoryView
The result looks good (matches the background in both light and dark mode).
I'd prefer if I could define the interface in a XIB though, I find that easier. But the property inputViewStyle is read-only, so it seems you can only set the inputView style during initialization.
I tried subclassing UIInputView and overriding inputViewStyle but that doesn't change the style for some reason.
Is there a way to init this with the .keyboard style and still use a xib?
You can load your XIB view and add it as a subview to your UIInputView:
class InputTestViewController: UIViewController {
#IBOutlet var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
let keyboardAccessoryView = UIInputView(frame: .init(x: 0, y: 0, width: 0, height: 40), inputViewStyle: .keyboard)
// I have MyInputView configured to load itself from a XIB
let v = MyInputView()
// add it to the UIInputView
keyboardAccessoryView.addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
v.topAnchor.constraint(equalTo: keyboardAccessoryView.topAnchor),
v.leadingAnchor.constraint(equalTo: keyboardAccessoryView.leadingAnchor),
v.trailingAnchor.constraint(equalTo: keyboardAccessoryView.trailingAnchor),
v.bottomAnchor.constraint(equalTo: keyboardAccessoryView.bottomAnchor),
])
textView.inputAccessoryView = keyboardAccessoryView
}
}
class MyInputView: UIView, NibLoadable {
override init(frame: CGRect) {
super.init(frame: frame)
setupFromNib()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupFromNib()
}
}
public protocol NibLoadable {
static var nibName: String { get }
}
public extension NibLoadable where Self: UIView {
static var nibName: String {
return String(describing: Self.self) // defaults to the name of the class implementing this protocol.
}
static var nib: UINib {
let bundle = Bundle(for: Self.self)
return UINib(nibName: Self.nibName, bundle: bundle)
}
func setupFromNib() {
guard let view = Self.nib.instantiate(withOwner: self, options: nil).first as? UIView else { fatalError("Error loading \(self) from nib") }
addSubview(view)
view.backgroundColor = .clear
view.frame = self.bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
}
Here's how my custom view looks in IB (four buttons in a horizontal stack view):
and here's how it looks at run-time:

XIB view not showing with correct layout [iOS Swift]

I'm trying to make a custom alert view and facing some issues with the fact that the displayed view is cutting the bottom half of the view (Images below)
How it's being displayed:
Desired Output:
So basically, I have a XIB called CustomAlertView supported by a class of same name with init as follows:
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
Bundle.main.loadNibNamed("CustomAlertView", owner: self, options: nil)
contentView.frame = self.bounds
contentView.translatesAutoresizingMaskIntoConstraints = true
addSubview(contentView)
//contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
I have another class that is responsible for creating an alert, CustomAlert, using the customAlertView. This CustomAlert class is creating the backgroundView and dialogView( which I'm trying to add my customAlertView to it) with the following code:
func initialize(title:String, description:String){
dialogView.clipsToBounds = true
backgroundView.frame = frame
backgroundView.backgroundColor = UIColor.black
backgroundView.alpha = 0.6
backgroundView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didTappedOnBackgroundView)))
addSubview(backgroundView)
dialogView.frame.origin = CGPoint(x: 0, y: 0)
dialogView.frame.size = CGSize(width: frame.width-32, height: frame.height/3)
dialogView.backgroundColor = UIColor.white
dialogView.layer.cornerRadius = 6
let alertView = CustomAlertView.init(frame: self.bounds)
alertView.titleLabel.text = title
alertView.descriptionLabel.text = description
alertView.cancelButton.backgroundColor = UIColor.brown
dialogView.addSubview(alertView)
addSubview(dialogView)
}
I believe that I'm making a confusion with the frames and bounds but couldn't find a solution.
I'd like the desired output to be placed perfectly inside the dialogView.
EDIT
Code for my .show function in CustomAlert
func show(animated:Bool){
self.backgroundView.alpha = 0
self.dialogView.center = CGPoint(x: self.center.x, y: self.frame.height + self.dialogView.frame.height/2)
UIApplication.shared.delegate?.window??.rootViewController?.view.addSubview(self)
if animated {
UIView.animate(withDuration: 0.33, animations: {
self.backgroundView.alpha = 0.66
})
UIView.animate(withDuration: 0.33, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 10, options: UIViewAnimationOptions(rawValue: 0), animations: {
self.dialogView.center = self.center
}, completion: { (completed) in
})
}else{
self.backgroundView.alpha = 0.66
self.dialogView.center = self.center
}
}
Github link git-alert-view
For anyone facing the same difficulties as me, I was able to accomplish the wanted result.
I used AutoLayouts as suggested by #HAK. But instead of writing my own NSLayoutConstraint I used roberthein library called TinyConstraints.
Basically, I used as follow:
Instead of
NSLayoutConstraint.activate([
alertView.topAnchor.constraint(equalTo: superview.topAnchor, constant: 0),
alertView.leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: 0),
alertView.bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: 0),
alertView.trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant:
0)])
with TinyConstraints:
alertView.edges(to: superview)
That´s it
Change your CustomAlertView like this:
class CustomAlertView: UIView {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var descriptionLabel: UILabel!
#IBOutlet weak var confirmButton: UIButton!
#IBOutlet weak var cancelButton: UIButton!
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
static func customAlert() -> CustomAlertView {
return Bundle.main.loadNibNamed("CustomAlertView", owner: self, options: nil)!.first as! CustomAlertView
}
}
Your CustomAlert's initialize method like this:
func initialize(title:String, description:String){
dialogView.clipsToBounds = true
backgroundView.frame = frame
backgroundView.backgroundColor = UIColor.black
backgroundView.alpha = 0.6
backgroundView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didTappedOnBackgroundView)))
addSubview(backgroundView)
dialogView.frame.origin = CGPoint(x: 0, y: 0)
dialogView.frame.size = CGSize(width: frame.width-32, height: frame.height/3)
dialogView.backgroundColor = UIColor.white
dialogView.layer.cornerRadius = 6
let alertView = CustomAlertView.customAlert()
alertView.titleLabel.text = title
alertView.descriptionLabel.text = description
alertView.cancelButton.backgroundColor = UIColor.brown
dialogView.addSubview(alertView)
addSubview(dialogView)
}
In the CustomAlertView xib:
1. Select fileowner and remove the class (default to NSObject).
2. Select fileowner and then remove all the outlets.
3. Select your content view and give it class = CustomAlertView.
4. Select your CustomAlertView and make all outlet connections.
Final Xib:
And you have a working alert:
PS: Adjust the UI accordingly.
In your xib class add this :
override func awakeFromNib() {
super.awakeFromNib()
xibSetup()}
func xibSetup() {
guard let view = loadViewFromNib() else { return }
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(view)
contentView = view
}
Reference from this : https://medium.com/zenchef-tech-and-product/how-to-visualize-reusable-xibs-in-storyboards-using-ibdesignable-c0488c7f525d#.3c0javomy**

iOS - How to initialize custom UIView with specific Frame from NIB

I am wondering what is the cleanest way to initialize a custom UIView with a specific frame.
The UIView is designed from a XIB file.
Here is my implementation :
class CustomView : UIView {
#IBOutlet var outletLabel: UILabel!
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
public override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
private func setupView() {
// Set text for labels
}
}
Here is how I want to initialize it in my ViewController :
let screenSize: CGRect = UIScreen.main.bounds
let screenWidth = screenSize.width
let frame = CGRect(x: 0, y: 0, width: screenWidth - 50, height: 70)
let customView = CustomView.init(frame: frame)
But it is not working, I have a white UIView without any outlets.
And if I do this instead :
// Extension to UIView to load Nib
let customView : CustomView = UIView.fromNib()
I can see my view from XIB file, with its width/height used in the Interface Builder.
What is I want to load the view from XIB file BUT with specific frame ?
Am I missing something about initialization ?
You can have a NibLoading class like:
// NibLoadingView.swift
//source:https://gist.github.com/winkelsdorf/16c481f274134718946328b6e2c9a4d8
import UIKit
// Usage: Subclass your UIView from NibLoadView to automatically load a xib with the same name as your class
#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
nibView.anchorAllEdgesToSuperview()
return nibView
}
}
extension UIView {
func anchorAllEdgesToSuperview() {
self.translatesAutoresizingMaskIntoConstraints = false
if #available(iOS 9.0, *) {
addSuperviewConstraint(constraint: topAnchor.constraint(equalTo: (superview?.topAnchor)!))
addSuperviewConstraint(constraint: leftAnchor.constraint(equalTo: (superview?.leftAnchor)!))
addSuperviewConstraint(constraint: bottomAnchor.constraint(equalTo: (superview?.bottomAnchor)!))
addSuperviewConstraint(constraint: rightAnchor.constraint(equalTo: (superview?.rightAnchor)!))
}
else {
for attribute : NSLayoutAttribute in [.left, .top, .right, .bottom] {
anchorToSuperview(attribute: attribute)
}
}
}
func anchorToSuperview(attribute: NSLayoutAttribute) {
addSuperviewConstraint(constraint: NSLayoutConstraint(item: self, attribute: attribute, relatedBy: .equal, toItem: superview, attribute: attribute, multiplier: 1.0, constant: 0.0))
}
func addSuperviewConstraint(constraint: NSLayoutConstraint) {
superview?.addConstraint(constraint)
}
}
Then your view will subclass the NibLoadingClass like:
class YourUIView: NibLoadingView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Set your XIB class in File's Owner like:
In this case it will be YourUIView
Then instantiate it:
let myView = YourUIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width-60, height: 170))
You can create custom class like this...
import UIKit
class CustomView: UIView {
class func mainView() -> CustomView {
let nib = UINib(nibName: "nib", bundle: nil)
let view = nib.instantiate(withOwner: self, options: nil).first as! CustomView
return view
}
override init(frame: CGRect) {
super.init(frame: frame)
let view = CustomView.mainView()
view.frame = frame
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Initialize view where you want...
let view = CustomView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.backgroundColor = UIColor.blue
self.view.addSubview(view)

Custom subview not loading in xib

I'm trying to create a button inside the textfield , so I created a button and call the function in xibSetup(). While running the program, the function is get called . But not loading in the App. Got stuck in it.
class NormalLogin: UIView {
weak var delegate:NormalLoginDelegate?
#IBOutlet weak var passwordField: UnderLinedTextField!
var view: UIView!
func showHideButton() {
let btn = UIButton(frame: CGRect(x: self.frame.width - 70 , y: self.frame.size.height - 20 , width: 50, height: 20))
btn.backgroundColor = UIColor.blackColor()
btn.layer.borderWidth = 5
btn.layer.borderColor = UIColor.blackColor().CGColor
btn.setTitle("Click Me", forState: UIControlState.Normal)
self.passwordField.addSubview(btn) // add to view as subview
}
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
func xibSetup() {
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
showHideButton() // Custom button i made
addSubview(view)
}
func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "NormalLogin", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
}
Did I do anything wrong ?
I guess it's happening because you are giving wrong frame to button
try this
let btn = UIButton(frame: CGRect(x: self.passwordField.frame.width - 70 , y: self.passwordField.frame.height - 20 , width: 50, height: 20))

Resources