Button loaded from xib addTarget not performing action - ios

I'm having a problem when I want to add a target to a button from a loaded xib.
I have this:
var cleanFilters = FilterLabelView()
override func viewWillAppear(_ animated: Bool) {
navigationItem.title = "EXPLORE WORKOUTS"
self.navigationController!.navigationBar.titleTextAttributes = [NSFontAttributeName: UIFont(name: "OpenSans-CondensedBold", size: 16.0)!]
setFilterLabel()
}
func setFilterLabel() {
cleanFilters = (Bundle.main.loadNibNamed("FilterLabelView", owner: self, options: nil)?.first as? FilterLabelView)!
self.view.addSubview(cleanFilters)
cleanFilters.translatesAutoresizingMaskIntoConstraints = false
self.view.addConstraint(NSLayoutConstraint(item: cleanFilters, attribute: .top, relatedBy: .equal, toItem: self.topLayoutGuide, attribute: .bottom, multiplier: 1, constant: 30))
self.view.addConstraint(NSLayoutConstraint(item: cleanFilters, attribute: .trailingMargin, relatedBy: .equal, toItem: self.view, attribute: .trailingMargin, multiplier: 1, constant: 35))
cleanFilters.deleteButton.addTarget(self, action: #selector(hideFilterLabel), for: .touchUpInside)
cleanFilters.confirmButton.addTarget(self, action: #selector(setDefaultFilters), for: .touchUpInside)
cleanFilters.isHidden = defaultsManager.isDefaultFilters()
if !cleanFilters.isHidden {
self.workoutsCollection.isUserInteractionEnabled = false
}
}
func hideFilterLabel() {
cleanFilters.isHidden = true
self.workoutsCollection.isUserInteractionEnabled = true
}
func setDefaultFilters() {
defaultsManager.setDefaultFilters()
cleanFilters.isHidden = true
getAllWorkouts()
}
The deleteButton and confirmButton actions are not being called and I can't figure out why.
Here's the FilterLabelView I'm loading:
import UIKit
class FilterLabelView: UIView {
#IBOutlet weak var deleteButton: UIButton!
#IBOutlet weak var confirmButton: UIButton!
#IBOutlet weak var labelTapRecongnizer: UITapGestureRecognizer!
}

Are you sure that your buttons are not hidden? remember that if a button is hidden the hittest can't perform touches... is it also posible your buttons are really little or have isUserInteractionEnabled as false... A good way to be aware whether is a problem with the view is looking out the Debug view hierarchy

Related

Designable view not rendered at correct position in Storyboard

I have a custom designable view class that looks like this:
#IBDesignable
class AuthInputView: UIView {
static let nibName = "AuthInputView"
#IBOutlet weak var mainContainerView: UIView!
#IBOutlet weak var mainStackView: UIStackView!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var errorLabel: UILabel!
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
fromNib()
}
override init(frame: CGRect) {
super.init(frame: frame)
fromNib()
}
override func awakeFromNib() {
super.awakeFromNib()
}
}
and a corresponding nib called AuthInputView that has its File's Owner set to AuthInputView.
And I have have a view controller designed in storyboard that has a view, who's class is set to AuthInputView. When I run an application it renders fine, but when I look at it in a storyboard, it looks like this:
Designables are also up to date :
but as can be seen, a custom view is rendered in an incorrect position (top left corner).
The code I use to load from nib and to attach required constraints after a content of a nib is added to a specified view looks like this:
extension UIView {
#discardableResult
func fromNib<T : UIView>() -> T? {
guard let contentView = Bundle(for: type(of: self)).loadNibNamed(String(describing: type(of: self)), owner: self, options: nil)?.first as? T else {
return nil
}
self.addSubview(contentView)
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.layoutAttachAll(to: self)
return contentView
}
func layoutAttachAll(to childView:UIView)
{
var constraints = [NSLayoutConstraint]()
childView.translatesAutoresizingMaskIntoConstraints = false
constraints.append(NSLayoutConstraint(item: childView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1.0, constant: 0))
constraints.append(NSLayoutConstraint(item: childView, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1.0, constant: 0))
constraints.append(NSLayoutConstraint(item: childView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0))
constraints.append(NSLayoutConstraint(item: childView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 0))
childView.addConstraints(constraints)
}
}
what is causing this misplacement in a storyboard view?
While many people like to use "layout helper" functions, it's easy to get confused...
You are calling your layoutAttachAll func with:
contentView.layoutAttachAll(to: self)
but in that function, you are doing this:
func layoutAttachAll(to childView:UIView)
{
var constraints = [NSLayoutConstraint]()
constraints.append(NSLayoutConstraint(item: childView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1.0, constant: 0))
...
but you've passed self as childView, so you're constraining self to self.
If you put your constraint code "inline":
func fromNib<T : UIView>() -> T? {
guard let contentView = Bundle(for: type(of: self)).loadNibNamed(String(describing: type(of: self)), owner: self, options: nil)?.first as? T else {
return nil
}
self.addSubview(contentView)
var constraints = [NSLayoutConstraint]()
contentView.translatesAutoresizingMaskIntoConstraints = false
constraints.append(NSLayoutConstraint(item: contentView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1.0, constant: 0))
constraints.append(NSLayoutConstraint(item: contentView, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1.0, constant: 0))
constraints.append(NSLayoutConstraint(item: contentView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0))
constraints.append(NSLayoutConstraint(item: contentView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 0))
self.addConstraints(constraints)
return contentView
}
you should no longer get the "misplaced" view.
If you really want to use your layoutAttachAll function, you want to call it with:
self.layoutAttachAll(to: contentView)
and change the last line:
// adding to wrong view
//childView.addConstraints(constraints)
self.addConstraints(constraints)
Maybe worth noting, you can vastly simplify your "helper" extension to:
extension UIView {
#discardableResult
func fromNib<T : UIView>() -> T? {
guard let contentView = Bundle(for: type(of: self)).loadNibNamed(String(describing: type(of: self)), owner: self, options: nil)?.first as? T else {
return nil
}
self.addSubview(contentView)
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
contentView.frame = bounds
return contentView
}
}

Add button programmatically

I want to make a button appear when there are 1 or more characters in a text field.
Currently, I have 3 problems:
I can't use the #objc if I do that then I get the following error "#objc can only be used with members of classes, #objc protocols, and concrete extensions of classes".
So basically I can't use the button.
I want to add constraints to the button, but on some of those I want the Safe Area to be the "toItem" but I don't know which "name" it has for that, I used view.safeAreaInsets but I'm not sure if that's the right way to do it.
I want to check how many characters there are in a text field (so I want the button to appear the moment that the user types a letter).
I would think it has to be done in an if statement but I don't know how to check for the characters in a text field.
This is my code for the first problem:
addButton.addTarget(self, action: #selector(buttonAction(_ :)), for: .touchUpInside)
self.view.addSubview(addButton)
#objc func buttonAction(_ : UIButton) {
}
This is for the second one:
let buttonRightConstraint = NSLayoutConstraint(item: addButton, attribute: .right, relatedBy: NSLayoutConstraint.Relation.equal, toItem: view.safeAreaInsets, attribute: .right, multiplier: 1.0, constant: 16)
let buttonTopConstraint = NSLayoutConstraint(item: addButton, attribute: .top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: view.safeAreaInsets , attribute: .top , multiplier: 1.0, constant: 16)
This is for the third one:
if (TextFieldName.text!.count > 0) {
}
This is my total code:
import UIKit
extension AddWorkoutController {
func hideKeyboard() {
let tap:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
view.addGestureRecognizer(tap)
}
#objc func dismissKeyboard() {
view.endEditing(true)
}
}
class AddWorkoutController: UIViewController {
#IBOutlet weak var TextFieldName: UITextField!
let addButton = UIButton(type: UIButton.ButtonType.custom) as UIButton
override func viewDidLoad() {
super.viewDidLoad()
configureTextFields()
self.hideKeyboard()
addButton.setBackgroundImage(UIImage(named: "Add-Button"), for: UIControl.State.normal)
addButton.setTitle("", for: UIControl.State.normal)
let buttonRightConstraint = NSLayoutConstraint(item: addButton, attribute: .right, relatedBy: NSLayoutConstraint.Relation.equal, toItem: view.safeAreaInsets, attribute: .right, multiplier: 1.0, constant: 16)
let buttonTopConstraint = NSLayoutConstraint(item: addButton, attribute: .top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: view.safeAreaInsets , attribute: .top , multiplier: 1.0, constant: 16)
let buttonHeightConstraint = NSLayoutConstraint(item: addButton, attribute: .height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 32)
let buttonWidthConstraint = NSLayoutConstraint(item: addButton, attribute: .width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 64)
addButton.addTarget(self, action: #selector(buttonAction(_ :)), for: .touchUpInside)
self.view.addSubview(addButton)
self.view.addConstraints([buttonRightConstraint, buttonTopConstraint, buttonHeightConstraint, buttonWidthConstraint])
#objc func buttonAction(_ : UIButton) {
}
if (TextFieldName.text!.count > 0) {
}
}
private func configureTextFields () {
TextFieldName.delegate = self
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
extension UIViewController: UITextFieldDelegate {
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
Maybe it sounds like a stupid question but I'm a beginner.​
#objc func buttonAction(_ : UIButton)
is a function and she must be outside of viewDidLoad, at the same level
class AddWorkoutController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#objc func buttonAction(_ : UIButton) {
}
}
For me the best solution is to add your addButton in the storyboard with hidden property checked by default (select your button and then go to right panel > Show the attributes inspector > hidden property)
You must add func textFieldDidEndEditing(_ textField: UITextField) in your AddWorkoutController extension. Inside you can add your if (TextFieldName.text!.count > 0) and then change isHidden property of addButton (addButton.isHidden)
extension AddWorkoutController: UITextFieldDelegate {
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
func textFieldDidEndEditing(_ textField: UITextField) {
}
}
PS: Be carefull of your indentation for more lisibility and in general variable are written in camelCase You'll find some best practice here => https://github.com/raywenderlich/swift-style-guide

Best approach to access IBOutlets after calling NSBundle (with loadNibName) for a custom view

I am looking for good practice how to initialize subviews (e.g. text of labels, or buttons) of a custom view that are connected via IBOutlets.
The custom view's view controller is calling the xib file on init like this:
final class MenuDiscoveryListView : NSView, MenuDiscoveryListViewProtocol {
let C_NAME_XIB = "MenuDiscoveryList"
#IBOutlet weak var labelStatus: NSTextField!
#IBOutlet weak var stackList: NSStackView!
var presenter : MenuDiscoveryListPresenter?
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
xibInit(autoXibLoading: true)
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
xibInit(autoXibLoading: false)
}
/// Routine for initializating view
///
/// - Parameter loadXib: Select if auto-load from related xib file into the view container is desired. Select TRUE, if function is called from NSView's `init(frame frameRect: NSRect)`.
func xibInit(autoXibLoading loadXib : Bool = true) {
// Load xib item
if loadXib {
var topLevelObjects : NSArray?
if Bundle(for: type(of: self)).loadNibNamed(C_NAME_XIB, owner: self, topLevelObjects: &topLevelObjects) {
if let contentView = topLevelObjects!.first(where: { $0 is NSView }) as? NSView {
// Add loaded view from xib file into container as subview
self.addSubview(contentView)
// Transfer dimensions
self.frame = contentView.frame
// Define constraints
self.translatesAutoresizingMaskIntoConstraints = false
contentView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint(item: self, attribute: .leading, relatedBy: .equal, toItem: contentView, attribute: .leading, multiplier: 1.0, constant: 0).isActive = true
NSLayoutConstraint(item: self, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1.0, constant: 0).isActive = true
NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1.0, constant: 0).isActive = true
NSLayoutConstraint(item: self, attribute: .bottom, relatedBy: .equal, toItem: contentView, attribute: .bottom, multiplier: 1.0, constant: 0).isActive = true
}
}
}
}
}
The init of the view controller is called from another view controller's presenter module in a very classy way:
let view = MenuHeaderItemView()
However, after initializing the view controller, as expected, the IBOutlets found nil. Nevertheless, I wanted to set a string value of labelStatus right after initializing the view (e.g. standard string) through NSBundle's (or Bundle's) loadNibName without waiting for awakeFromNib.
What is a good practice or approach to do this synchronously and access the IBOutlets right after the init?
EDIT:
I have realized that labelStatus and stackList are successfully loaded in contentView:
Is there any elegant way to copy their content/instantiation over to the IBOutlets?
with the statement
let view = MenuHeaderItemView()
The view controller has not yet loaded its view hierarchy/ Subviews.
From my understanding you may use:
let customView = Bundle.main.loadNibNamed("MenuDiscoveryList", owner: nil,
options: nil)?[0] as? MenuDiscoveryListView
if let menuView = customView {
menuView.labelStatus.text = "You label string"
}
Thanks, have a try with this.

How to unwrap programmed UIButton (swift4)

I am trying to create a button using programmed constraints. I am trying to not use the storyboard. I am having trouble unwrapping the button. How do I do this?
import UIKit
var aa: [NSLayoutConstraint] = []
class ViewController: UIViewController {
var btn: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(btn)
let leadingc2 = btn.widthAnchor.constraint(equalToConstant: 80)
let trailingC2 = btn.heightAnchor.constraint(equalToConstant: 50)
let topc2 = btn.centerXAnchor.constraint(equalTo: self.view.centerXAnchor, constant: -50)
let bottomc2 = btn.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: -250)
aa = [leadingc2,trailingC2,topc2,bottomc2]
NSLayoutConstraint.activate(aa)
}
}
You do not need to unwrap it. You need to instantiate it before using it.
override func viewDidLoad() {
super.viewDidLoad()
btn = UITextField() // Create the button like this before using it.
self.view.addSubview(btn)
btn.translatesAutoresizingMaskIntoConstraints = false
let leadingc2 = btn.widthAnchor.constraint(equalToConstant: 80)
let trailingC2 = btn.heightAnchor.constraint(equalToConstant: 50)
let topc2 = btn.centerXAnchor.constraint(equalTo: self.view.centerXAnchor, constant: -50)
let bottomc2 = btn.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: -250)
aa = [leadingc2,trailingC2,topc2,bottomc2]
NSLayoutConstraint.activate(aa)
}
Any variable declared with ! will be force unwrapped, meaning that if you forget to create an instance and use the variable, it will throw an error and crash your app.
Use this code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(button)
setupConstraints()
}
lazy var button: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Button", for: .normal)
button.backgroundColor = .blue
//your action here
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
return button
}()
private func setupConstraints() {
button.translatesAutoresizingMaskIntoConstraints = false
let top = NSLayoutConstraint(item: button, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 100)
let left = NSLayoutConstraint(item: button, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1, constant: 50)
let right = NSLayoutConstraint(item: button, attribute: .right, relatedBy: .equal, toItem: view, attribute: .right, multiplier: 1, constant: -50)
let height = NSLayoutConstraint(item: button, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 50)
view.addConstraints([top, left, right, height])
}
#objc func buttonAction() {
print("Button has pressed")
}
}

Customize UISearchBar in Swift 4

I need to delete the background of the search bar.
The part where text is entered (white color) make it higher.
put a green border around the white part.
customize the font.
I need this:
What I have achieved is this:
My code:
#IBOutlet weak var searchBar: UISearchBar!
override func viewDidLoad() {
super.viewDidLoad()
self.searchBar.layer.borderColor = #colorLiteral(red: 0.2352941176, green: 0.7254901961, blue: 0.3921568627, alpha: 1)
self.searchBar.layer.borderWidth = 1
self.searchBar.clipsToBounds = true
self.searchBar.layer.cornerRadius = 20
}
Try using this code:
class JSSearchView : UIView {
var searchBar : UISearchBar!
override func awakeFromNib()
{
// the actual search barw
self.searchBar = UISearchBar(frame: self.frame)
self.searchBar.clipsToBounds = true
// the smaller the number in relation to the view, the more subtle
// the rounding -- https://www.hackingwithswift.com/example-code/calayer/how-to-round-the-corners-of-a-uiview
self.searchBar.layer.cornerRadius = 5
self.addSubview(self.searchBar)
self.searchBar.translatesAutoresizingMaskIntoConstraints = false
let leadingConstraint = NSLayoutConstraint(item: self.searchBar, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 20)
let trailingConstraint = NSLayoutConstraint(item: self.searchBar, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: -20)
let yConstraint = NSLayoutConstraint(item: self.searchBar, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0)
self.addConstraints([yConstraint, leadingConstraint, trailingConstraint])
self.searchBar.backgroundColor = UIColor.clear
self.searchBar.setBackgroundImage(UIImage(), for: .any, barMetrics: .default)
self.searchBar.tintColor = UIColor.clear
self.searchBar.isTranslucent = true
// https://stackoverflow.com/questions/21191801/how-to-add-a-1-pixel-gray-border-around-a-uisearchbar-textfield/21192270
for s in self.searchBar.subviews[0].subviews {
if s is UITextField {
s.layer.borderWidth = 1.0
s.layer.cornerRadius = 10
s.layer.borderColor = UIColor.green.cgColor
}
}
}
override func draw(_ rect: CGRect) {
super.draw(rect)
// the half height green background you wanted...
let topRect = CGRect(origin: .zero, size: CGSize(width: self.frame.size.width, height: (self.frame.height / 2)))
UIColor.green.set()
UIRectFill(topRect)
}
}
And to use it, drop a custom view into your xib or storyboard file and simply set the custom class type to the name of the class (JSSearchView).

Resources