Swift: Unrecognized selector sent to class, buttons target in NSObject class - ios

pls do not mark this question as duplicate as I have tried the various solutions but doesn't quite work. I am creating a headerView in my FormViewController (I'm using Eureka) and I separated my views into a separate HeaderViews: NSObject to observe the MVC principle. I attempted to add a buttonTarget but it throws the error unrecognized selector sent to class. My code as follows:
class ProfileSettingsViewController: FormViewController {
override func viewDidLoad() {
super.viewDidLoad()
form +++
Section(){ section in
section.header = {
var header = HeaderFooterView<UIView>(.callback({
let view = HeaderViews.setUpHeaderForProfileView(user: self.user)
return view
}))
header.height = { 200 }
return header
}()
}
}
And in my HeaderViews:
class HeaderViews: NSObject {
static func setUpHeaderForProfileView(user: User) -> UIView {
let view = UIView(frame: CGRect(x: 0, y: 0, width: screen.width, height: 100))
let changeAvatarButton = UIButton(frame: CGRect(x: (screen.width / 2) - 50, y: avatarImageView.frame.maxY + 10, width: 100, height: 20))
changeAvatarButton.addTarget(self, action: #selector(getAvatar(_:)), for: .touchUpInside)
view.addSubview(changeAvatarButton)
return view
}
func getAvatar(_ sender: UIButton!) {
print("Change Avatar tapped")
}
}
I'm not quite sure if I have made any mistake, as I have tried various methods of the selectors in this post: Unrecognized selector sent to class
I suspect it might something to do with the subclass NSObject. Any advice pls?

Update
If you could not get instance of HeaderViews. then just change getAvatar to static funciton.
static func getAvatar(_ sender: UIButton!) {
print("Change Avatar tapped")
}
Origin
You should not use self as target in static function.
Because this self means HeaderViews.
Passing instance to function as target.
Ex:
static func setUpHeaderForProfileView(user: User, in instance: HeaderViews) -> UIView {
let view = UIView(frame: CGRect(x: 0, y: 0, width: screen.width, height: 100))
let changeAvatarButton = UIButton(frame: CGRect(x: (screen.width / 2) - 50, y: avatarImageView.frame.maxY + 10, width: 100, height: 20))
changeAvatarButton.addTarget(instance, action: #selector(getAvatar(_:)), for: .touchUpInside)
view.addSubview(changeAvatarButton)
return view
}

If you are not using the UIButton Parameter, define func getAvatar() with no parameters or func getAvatar(_ sender: AnyObject?)
The function call would change to this.
changeAvatarButton.addTarget(self, action: #selector(getAvatar), for: .touchUpInside)
Also I am curious why not define HeaderViews as a subclass of UIView?
That might actually be the problem here.

Related

Assign tags for dynamic UIButtons

I found this example of a dynamic view of UIButtons at: http://helpmecodeswift.com/advanced-functions/generating-uibuttons-loop
I inserted this code into my app but unfortunately I can't find a way to set a tag for each buttons.
My target is to know which button is tapped, count the amount of clicks for each button and save this information into my DB.
override func viewDidLoad() {
super.viewDidLoad()
var arrayOfVillains = ["santa", "bugs", "superman", "batman"]
var buttonY: CGFloat = 20
for villain in arrayOfVillains {
let villainButton = UIButton(frame: CGRect(x: 50, y: buttonY, width: 250, height: 30))
buttonY = buttonY + 50 // we are going to space these UIButtons 50px apart
villainButton.layer.cornerRadius = 10 // get some fancy pantsy rounding
villainButton.backgroundColor = UIColor.darkGrayColor()
villainButton.setTitle("Button for Villain: \(villain)", forState: UIControlState.Normal) // We are going to use the item name as the Button Title here.
villainButton.titleLabel?.text = "\(villain)"
villainButton.addTarget(self, action: "villainButtonPressed:", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(villainButton) // myView in this case is the view you want these buttons added
}
}
func villainButtonPressed(sender:UIButton!) {
if sender.titleLabel?.text != nil {
println("You have chosen Villain: \(sender.titleLabel?.text)")
} else {
println("Nowhere to go :/")
}
}
}
So how it is possible to set and get the tag for/from each button in code (in this example)?
Thank you in advance!
You can use enumerated array to access indexes of the current item in iteration and use that as a tag:
override func viewDidLoad() {
super.viewDidLoad()
var arrayOfVillains = ["santa", "bugs", "superman", "batman"]
var buttonY: CGFloat = 20
for (index, villain) in arrayOfVillains.enumerated() {
let villainButton = UIButton(frame: CGRect(x: 50, y: buttonY, width: 250, height: 30))
buttonY = buttonY + 50 // we are going to space these UIButtons 50px apart
// set the tag to current index
villainButton.tag = index
villainButton.layer.cornerRadius = 10 // get some fancy pantsy rounding
villainButton.backgroundColor = UIColor.darkGrayColor()
villainButton.setTitle("Button for Villain: \(villain)", forState: UIControlState.Normal) // We are going to use the item name as the Button Title here.
villainButton.titleLabel?.text = "\(villain)"
villainButton.addTarget(self, action: "villainButtonPressed:", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(villainButton) // myView in this case is the view you want these buttons added
}
}
}
#objc func villainButtonPressed(sender:UIButton!) {
// tag of pressed button
print(">>> you pressed button with tag: \(sender.tag)")
if sender.titleLabel?.text != nil {
println("You have chosen Villain: \(sender.titleLabel?.text)")
} else {
println("Nowhere to go :/")
}
}
You should use an integer-based for loop for this.
for i in 0 ..< arrayOfVillains.count {
let villain = arrayOfVillains[i]
let villainButton = UIButton(frame: CGRect(x: 50, y: buttonY, width: 250, height: 30))
villainButton.tag = i
}
You can do as follows,
var buttonTag = 1
for villain in arrayOfVillains {
let villainButton = UIButton(frame: CGRect(x: 50, y: buttonY, width: 250, height: 30))
villainButton.tag = buttonTag
buttonTag += 1
}

Calling a function (containing variable) in another class

I have a relatively simple question. I created a function - covers the contents to retrieve data from the API. I need to call a function in several different classes (don't want to duplicate). I am calling that function, but nothing is getting displayed. If this function is a part of class where I want to call it, it works correctly. Where is the problem, please let me know. (I think in UIView...)? Thank you.
My function:
func loadScreen() {
var loadView:UIView!
loadView = UIView(frame: self.view.frame)
loadView!.backgroundColor = UIColor.black
loadView!.alpha = 1.0
let actView = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
actView.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
actView.center = CGPoint(x: self.view.bounds.size.width / 2 , y: self.view.bounds.size.height / 2 )
actView.startAnimating()
loadView?.addSubview(actView)
self.view.addSubview(loadView!)
}
I call a function in another class in viewDidLoad (), but as I said, nothing is displayed.
First create a singleton class like this
Singleton class
import UIKit
import Foundation
class Singleton: NSObject
{
static let sharedInstance = Singleton()
override init()
{
super.init()
}
func placeBlockerView(target : UIViewController)
{
var loadView:UIView!
loadView = UIView(frame: target.view.frame)
loadView!.backgroundColor = UIColor.black
loadView!.alpha = 1.0
let actView = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
actView.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
actView.center = CGPoint(x: target.view.bounds.size.width / 2 , y: target.view.bounds.size.height / 2 )
actView.startAnimating()
loadView?.addSubview(actView)
target.view.addSubview(loadView!)
}
}
After that use it from where ever you want as required
like this
Singleton.sharedInstance.placeBlockerView(target: self)
This above line will add that UIView you have created to your view controller's view.
Let me know if it don't work.
Instead of adding it in the above, it is always advised to return the UIView from that method and in your subclass of UIViewController do the following.
func viewDidLoad() {
let loadView = loadScreen()
self.view.addSubView()
}

Unable to change contentSize of UIScrollview in WKWebView

I'm trying to add a little bit of extra height to the content of a UIScrollView that is within a WKWebView after it loads by adjusting the contentSize property.
I can modify this property, but it somehow keeps changing back to its original size by the time the next layout/display refresh hits.
To test this even further, I attempted to change contentSize in scrollViewDidScroll. Whenever you scroll to the bottom, you can see for a fraction of a second that it's trying add the extra space and keeps reverting back.
I can't reproduce this issue with UIWebView. It works just fine there. Perhaps some changes were added to WKWebView recently? I'm using Xcode 8 and testing on iOS 9/10.
Given my ineptitude with Dropbox I felt badly so put the attached together to try and help you out. If you change the contentInset property of the WKWebView's scrollView rather than contentSize, this seems to work quite well. I agree with you that while you might be able temporarily to change the content size of the scrollView, it reverts quickly; moreover, there are no delegate methods either for UIScrollView or WKWebView that I can find that you might override to counteract this.
The following sample code has a web page and some buttons that allow you to increase or decrease the top and bottom contentInset and animating you to the appropriate point on the scrollView.
import UIKit
import WebKit
class ViewController: UIViewController {
var webView : WKWebView!
var upButton : UIButton!
var downButton : UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let webFrame = CGRect(origin: CGPoint(x: 100, y: 100), size: CGSize(width: self.view.frame.width - 200, height: self.view.frame.height - 200))
webView = WKWebView(frame: webFrame)
webView.load(URLRequest(url: URL(string: <PUT RELEVANT URL STRING (NB - THAT YOU ARE SURE IS VALID) HERE>)!))
webView.backgroundColor = UIColor.red
webView.scrollView.contentMode = .scaleToFill
self.view.addSubview(webView)
func getButton(_ label: String) -> UIButton {
let b : UIButton = UIButton()
b.setTitle(label, for: .normal)
b.setTitleColor(UIColor.black, for: .normal)
b.layer.borderColor = UIColor.black.cgColor
b.layer.borderWidth = 1.0
return b
}
let upButton = getButton("Up")
let downButton = getButton("Down")
upButton.frame = CGRect(origin: CGPoint(x: 25, y: 25), size: CGSize(width: 50, height: 50))
downButton.frame = CGRect(origin: CGPoint(x: 25, y: 100), size: CGSize(width: 50, height: 50))
upButton.addTarget(self, action: #selector(increaseContentInset), for: .touchUpInside)
downButton.addTarget(self, action: #selector(decreaseContentInset), for: .touchUpInside)
self.view.addSubview(webView)
self.view.addSubview(upButton)
self.view.addSubview(downButton)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func increaseContentInset() -> Void {
guard let _ = webView else { return }
webView.scrollView.contentInset = UIEdgeInsetsMake(webView.scrollView.contentInset.top + 100, 0, webView.scrollView.contentInset.bottom + 100, 0)
webView.scrollView.setContentOffset(CGPoint(x: webView.scrollView.contentInset.left, y: -1 * webView.scrollView.contentInset.top), animated: true)
}
func decreaseContentInset() -> Void {
guard let _ = webView else { return }
webView.scrollView.contentInset = UIEdgeInsetsMake(webView.scrollView.contentInset.top - 100, 0, webView.scrollView.contentInset.bottom - 100, 0)
webView.scrollView.setContentOffset(CGPoint(x: webView.scrollView.contentInset.left, y: -1 * webView.scrollView.contentInset.top), animated: true)
}
}
I hope that helps. If you need an answer based specifically on setting the content size then let me know, but I think this is the best option.

How to switch custom in-app keyboards

Recently I learned to make custom in-app keyboards. Now I want to be able to swap between multiple custom keyboards. However, resetting the textField.inputView property does not seem to work.
I recreated a simplified version of this problem in the following project. The UIViews represent actual custom keyboards.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
let blueInputView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 300))
blueInputView.backgroundColor = UIColor.blueColor()
textField.inputView = blueInputView
textField.becomeFirstResponder()
}
#IBAction func changeInputViewButtonTapped(sender: UIButton) {
let yellowInputView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 300))
yellowInputView.backgroundColor = UIColor.yellowColor()
// this doesn't cause the view to switch
textField.inputView = yellowInputView
}
}
Running this gives what I would initially expect: a blue input view pops up.
But when I tap the button to switch to the yellow input view, nothing happens. Why? What do I need to do to get this to work?
After a little more experimenting I have the solution now. I needed to resign the first responder and then set it again. Any first responder that is a subview of the top view can be resigned indirectly by calling endEditing.
#IBAction func changeInputViewButtonTapped(sender: UIButton) {
let yellowInputView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 300))
yellowInputView.backgroundColor = UIColor.yellowColor()
// first do this
self.view.endEditing(true)
// or this
//textField.resignFirstResponder()
textField.inputView = yellowInputView
textField.becomeFirstResponder()
}
Thanks to this and this answer for the ideas.

How create buttonAction functions dynamically in Swift

In a MCQ app I create multiple UIButton programatically like this
func createNewButton(buttonName: String, xPosition: CGFloat, yPosition: CGFloat) -> UIButton {
let myButton = UIButton(frame: CGRectMake(0, 0, 50, 50))
myButton.center = CGPoint(x: xPosition, y: yPosition)
myButton.setImage(UIImage(named: buttonName), forState: UIControlState.Normal)
myButton.addTarget(self, action: "buttonName:", forControlEvents: UIControlEvents.TouchUpInside)
return myButton
}
The problem is that in order to match a function to these buttons I need to create a function. The function should therefore looks like
func buttonName(sender: UIButton!) {
// do some stuff
}
These functions are actually going to save a string in an array, and the string to save is the name of the function itself. If we have for example buttonName: "blackberry", the code is going to set an image called "blackberry" and add a target action named "blackberry" and I would like therefore a buttonAction function called func blackberry(sender: UIButton) and this function would save the string "blackberry" in an array of String.
But all of this should be done in function of the initial buttonName: String parameter.
Or maybe I am really not doing things the right way, and how should I do then?
How about this instead. Send all of your buttons to processButton(button: UIButton). When you create your buttons, set the tag property assigning a unique number to each button. Then you can have an array which translates the button tag to the string.
var buttonNames: [String] = []
var currentIndex = 0
func createNewButton(buttonName: String, xPosition: CGFloat, yPosition: CGFloat) -> UIButton {
let myButton = UIButton(frame: CGRectMake(0, 0, 50, 50))
myButton.center = CGPoint(x: xPosition, y: yPosition)
myButton.setImage(UIImage(named: buttonName), forState: UIControlState.Normal)
myButton.addTarget(self, action: "processButton:", forControlEvents: UIControlEvents.TouchUpInside)
myButton.tag = currentIndex++
self.buttonNames.append(buttonName)
return myButton
}
func processButton(button: UIButton) {
let string = buttonNames[button.tag]
// process string
}

Resources