My class:
class SelectBox {
internal static func openSelector(list:[String: String], parent:UIView){
print("iosLog HELLO")
parent.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleClick(sender:))))
}
#objc func handleClick(sender: UITapGestureRecognizer) {
print("iosLog CLICK")
}
}
Set view :
SelectBox.openSelector(list: AppDelegate.stateList, parent: bgPlaceInto)
After launch print HELLO, but after click on view i get below error :
2018-07-07 18:39:12.298322+0430 Ma[971:260558] [ChatService]: SMT:
2018-07-07 18:39:12.470392+0430
Ma[971:260525] [ChatService]: RCV: 2018-07-07 18:39:12.471851+0430
Ma[971:260591] [ChatService]: RCV:
2018-07-07 18:39:14.674675+0430 Ma[971:260392] *** NSForwarding:
warning: object 0x100a9fc70 of class 'Ma.SelectBox' does not implement
methodSignatureForSelector: -- trouble ahead Unrecognized selector
+[Ma.SelectBox handleClickWithSender:] 2018-07-07 18:39:14.675210+0430 Ma[971:260392] Unrecognized selector +[Ma.SelectBox
handleClickWithSender:]
How i can set click listener to view by class?
Thank you
Your openSelector method is static. The word self in a static context, refers to an instance of the surrounding type's meta type. In this case, SelectorBox.Type.
Obviously, SelectorBox.Type does not have a handleClick method. SelectorBox does.
You need to make the openSelector method non-static:
internal func openSelector(list:[String: String], parent:UIView){
print("iosLog HELLO")
parent.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleClick(sender:))))
}
Now self refers to the SelectorBox instance.
You can call it like this:
// declare this at class level:
let box = SelectorBox()
// call the method like this
box.openSelector()
EDIT: Your class should look like this:
class ViewControllerPage: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var bgGenderInto: UIView!
let box = SelectBox()
override func viewDidLoad() {
super.viewDidLoad()
box.openSelector(list: AppDelegate.genderList, parent: bgGenderInto)
}
}
Related
I create nib class :
import UIKit
class AlertController: UIView {
#IBOutlet weak var back: UIView!
#IBOutlet weak var viewMain: UIView!
class func createAlert() -> AlertController {
let myNib = UINib(nibName: "Alert", bundle: nil)
let nib = myNib.instantiate(withOwner: nil, options: nil)[0] as! AlertController
nib.back.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleBack(sender:))))
return nib
}
#objc func handleBack(sender: UITapGestureRecognizer) {
//viewMain.isHidden = true
print("LogIos => X")
}
}
And use it on another class :
let view = AlertController.createAlert()
viewMain.addSubview(view)
This work fine, but if i click on back button i get below error:
2018-06-13 12:28:03.958448+0430 Mat[50493:243526] *** Terminating
app due to uncaught exception 'NSInvalidArgumentException', reason:
'+[Mat.AlertController handleBackWithSender:]: unrecognized selector
sent to class 0x104db4e90'
But if i use below code, work fine:
#IBAction func xxx(_ sender: Any) {
print("LogIos => Y")
}
I want use UITapGestureRecognizer because i use View for custom button
Although I do not understand what you mean with "func xxx works fine" (which should not, if you use the same code as above), the problem is that:
createAlert is a static (class) function
Inside
you create an instance nib of your AlertController
your #selector refers to self, which in this case is the class itself, not the instance
Therefore, the gesture recognizer refers to the static (class) function handleBack - which you can see when you take a close look at + sign in the exception text:
reason: '+[Mat.AlertController handleBackWithSender:]: unrecognized selector
Therefore, either make handleBack a static (class func), or provide an instance to as the target (which is what you want, as I would assume)
So
nib.back.addGestureRecognizer(UITapGestureRecognizer(target: nib, action: #selector(handleBack(sender:))))
should work for you.
Remark
Nevertheless, let me allow you a design hint: Although you name it AlertController, it is not a view controller, but a UIView - this is confusing. You should clearly separate view and controller, and separate which of those handles what.
Currently my MainViewController can connect to my Bluetooth module and read the data coming from it.
Now, I'm trying to read the data from another View Controller.
My Bluetooth Manager is a singleton so that it doesn't get instantiated multiple times. To read and process the data in the appropriate ViewController, I was thinking of using optional delegates. It's working fine when I get to receivedMVC(data: String) but crashes when getting to receivedUVC(data: String)
I get the following error:
[BLE_Tests.MainViewController receivedUVCWithData:]: unrecognized
selector sent to instance 0x100d0a9d0 2017-06-22 16:25:58.634682-0700
BLE_Tests[9544:2692419] * Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[BLE_Tests.MainViewController
**receivedUVCWithData:]: unrecognized selector sent to instance
0x100d0a9d0'
If I add the receivedUVC(data: String) to my MainViewController, it doesn't crash but doesn't call the receivedUVC from the correct ViewController.
How do I point to the correct selector?
Thank you.
MainViewController.swift
class MainViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, BluetoothDelegate {
#IBOutlet weak var peripheralListTableView: UITableView!
#IBOutlet weak var updateButton: UIButton!
let bluetoothManager = BluetoothManager.getInstance()
override func viewDidLoad() {
super.viewDidLoad()
peripheralListTableView.delegate = self
peripheralListTableView.dataSource = self
bluetoothManager.delegate = self
bluetoothManager.discover()
}
func reloadPeripheralList() {
peripheralListTableView.reloadData()
}
func receivedMVC(data: String) {
print("Hello? \(data)")
}
//MARK: - UITableViewDataSource
}
UpdateViewController.swift
class UpdateViewController: UIViewController, BluetoothDelegate {
let bluetoothManager = BluetoothManager.getInstance()
func receivedUVC(data: String) {
print("Allo: \(data)")
}
}
BluetoothManager.swift
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
let stringValue = String(data: characteristic.value!, encoding: String.Encoding.utf8)!
print("Received packet: \(stringValue)")
delegate?.receivedMVC!(data: stringValue) // Delegate method in MainViewController
delegate?.receivedUVC!(data: stringValue) // Delegate method in UpdateViewController
}
[BLE_Tests.MainViewController **receivedUVCWithData:]
This tells you that the MainViewController method receivedUVCWithData: was called, but that class do not implement it. And thats the reason why it's called:
delegate?.receivedUVC!(data: stringValue)
This call will check if the delegate exists and if so it send a message to receivedUVC that must(!) exist. So it will not crash if you call this:
delegate?.receivedUVC?(data: stringValue)
But then I ask myself why you define two different methods in your protocol? Define one mandatory (not optional) method in your protocol, implement it in both UIViewControllers and call this method in BluetoothManager.swift. Then the last set delegate get the data. If both ViewControllers exists at the same time your BluetoothManager needs a delegate1 and a delegate2 and a call to the method on both delegates.
I have the following code:
extension ViewController {
func AddLeftGesture(){
let SwipeLeft:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(MyDismissOnSwipeLeft))
self.view.addGestureRecognizer(SwipeLeft)
}
func MyDismissOnSwipeLeft(){
self.dismiss(animated: true, completion: nil)
}
and What I would like to accomplish is that override the viewDidLoad and
call AddLeftGesture method so that it'll be part of each VC I make
and I don't have to type it again and again in each viewDidLoad,
is this possible? or do you guys have any other suggestions?
well I don't think it's a good idea, because typically viewDidLoad is used for setting most properties and if you would like to override it in a view controller you should write it again.What I can suggest is that to make a base ViewController and add this code in the viewDidLoad of that and then subclass every viewController from the base view controller , This way whenever you want to change anything you just call super.viewDidLoad
class BaseViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
addLeftGesture()
}
}
class CustomViewController: BaseViewController{
}
Make this class which inherits UITapGestureRecognizer
open class BlockTap: UITapGestureRecognizer {
fileprivate var tapAction: ((UITapGestureRecognizer) -> Void)?
public override init(target: Any?, action: Selector?) {
super.init(target: target, action: action)
}
public convenience init (
tapCount: Int = 1,
fingerCount: Int = 1,
action: ((UITapGestureRecognizer) -> Void)?) {
self.init()
self.numberOfTapsRequired = tapCount
#if os(iOS)
self.numberOfTouchesRequired = fingerCount
#endif
self.tapAction = action
self.addTarget(self, action: #selector(BlockTap.didTap(_:)))
}
open func didTap (_ tap: UITapGestureRecognizer) {
tapAction? (tap)
}
}
then make an extension of UIView
extension UIView {
public func addTapGesture(tapNumber: Int = 1, action: ((UITapGestureRecognizer) -> ())?) {
let tap = BlockTap(tapCount: tapNumber, fingerCount: 1, action: action)
addGestureRecognizer(tap)
isUserInteractionEnabled = true
}
}
Then You can use this as
override func viewDidLoad() {
super.viewDidLoad()
self.view.addTapGesture(action: {[unowned self] (_) in
//Do whatever on click of View
})
}
Hope it helps!
There's two options AFAIK. Either you can subclass UIViewController and then make all of your controllers inherit from the subclassed one, or you can swizzle UIViewController's viewDidLoad().
I personally would choose swizzling, although it has one disadvantage - it hides the implementation and might be confusing for a new developer coming onto a project. So make sure you document this properly, somewhere in your project README and in the code as well.
Now for some code examples:
Subclassing UIViewController
MyViewController.swift
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
addGesture()
}
func addGesture() {
// Do what you need
}
}
class OtherViewController: MyViewController {
// Automatically will add gesture because it's a subclass of MyViewController
}
Swizzling viewDidLoad
What method swizzling does is, that it exchanges implementations of your methods. That simply means that the name of your function points at code from a different function. For more information on this topic read this article.
UIViewController+Swizzle.swift
static func swizzle(selector originalSelector: Selector,
with newSelector: Selector,
on targetClass: AnyClass) {
let originalMethod = class_getInstanceMethod(targetClass, originalSelector)
let swizzledMethod = class_getInstanceMethod(targetClass, newSelector)
// If we were able to add the swizzled function, replace methods.
// Otherwise exchange implementations if method already exists.
if class_addMethod(targetClass, originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod)) {
class_replaceMethod(targetClass, newSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
extension UIViewController {
// This function is getting called automatically by the runtime,
// when this class is loaded to perform some additional intiialization.
// However, this has now been deprecated in Swift, so only option is to
// declare a static function which you need to remember to call from
// somewhere, preferably early in your app initialization, like your
// didFinishLaunching function in AppDelegate or even AppDelegate's init
// function. I kept the initialize function in the code as a reference,
// however you would probably want to write it like in the comment
// below, to silence the warning.
//
// class func swizzle()
//
open override class func initialize() {
if self != UIViewController.self { return }
let swizzlingClosure: () = {
swizzle(selector: #selector(UIViewController.viewDidLoad),
with: #selector(UIViewController.swizzled_viewDidLoad),
on: UIViewController.self)
}()
swizzlingClosure
}
#objc private func swizzled_viewDidLoad() {
// Calls the original implementation,
// because implementations are switched.
swizzled_viewWillAppear(animated)
// Do whatever you need
addGesture()
}
#objc func addGesture() {
// Add your gesture
}
}
I have a Cocoa Touch Framework named FooFramework.
Within it, I want to manage the move up on the Y axis for selected views when the keyboard shows. I created a KeyboardManager class. Here's how it looks:
import UIKit
public class KeyboardManager {
var notifyFromObject: Any?
var observer: Any
public var viewsToPushUp: [UIView] = []
public init(observer: Any, viewsToPushUp: [UIView], notifyFromObject: Any? = nil) {
self.observer = observer
self.notifyFromObject = notifyFromObject
self.viewsToPushUp = viewsToPushUp
}
public func pushViewsUpWhenKeyboardWillShow(){
let notificationCenter = NotificationCenter.default
print(self)
notificationCenter.addObserver(self.observer, selector: #selector(FooFramework.KeyboardManager.pushViewsUp), name: NSNotification.Name.UIKeyboardWillShow, object: notifyFromObject)
}
#objc public func pushViewsUp(notification: NSNotification) {
if let keyboardRectValue = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
let keyboardHeight = keyboardRectValue.height
for view in viewsToPushUp {
view.frame.origin.y -= keyboardHeight
}
}
}
}
Then, I import this FooFramework in an iOS app named Bar. To test the FooFramework, I want to push up a UITextField. Here's the code:
import UIKit
import FooFramework
class ViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let kb = KeyboardManager(observer: self, viewsToPushUp: [textField], notifyFromObject: nil)
kb.pushViewsUpWhenKeyboardWillShow()
}
func pushViewsUp(notification: NSNotification) {
print("This should not be printed")
}
}
My problem is that This should not be printed appears in the console and the pushViewsUp method from the KeyboardManager never gets called. Even though I used a fully qualified name for the selector, it insists on using the pushViewsUp from the ViewController. This is driving me nuts.
If I remove pushViewsUp from the ViewController, I get the following error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Bar.ViewController pushViewsUpWithNotification:]: unrecognized selector sent to instance 0x7fc540702d80'
What do I need to do so the selector properly points to FooFramework.KeyboardManager.pushViewsUp?
I believe you need to use self instead of self.observer for the observer in the addObserver function.
Also you need to declare the kb variable outside the scope of the function in order for the manager to detect the notification.
Example:
import UIKit
import FooFramework
class ViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
var kb: KeyboardManager?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
kb = KeyboardManager(observer: self, viewsToPushUp: [textField], notifyFromObject: nil)
kb?.pushViewsUpWhenKeyboardWillShow()
}
}
KeyboardManager changes:
public func pushViewsUpWhenKeyboardWillShow() {
let notificationCenter = NotificationCenter.default
print(self)
notificationCenter.addObserver(self,
selector: #selector(FooFramework.KeyboardManager.pushViewsUp),
name: NSNotification.Name.UIKeyboardWillShow,
object: notifyFromObject)
}
Other than what Justin has suggested which is all correct, there are a few more things to consider before you fully solve the problem.
KeyBoardManager class instance itself is going to observe the keyboardWillMoveUp notification so your
var observer: Any
within it is unnecessary. You should remove that.
I would also put the addObserver part right in the init of KeyBoardManager class itself so that this extra call pushViewsUpWhenKeyboardWillShow() can be avoided which seems to be doing nothing but that. Since the KeyBoardManager class is supposed to be doing only this, I don't see why adding observer should be another function call.
So this is how your KeyboardManager class should look:
import UIKit
public class KeyboardManager {
var notifyFromObject: Any?
public var viewsToPushUp: [UIView] = []
public init(viewsToPushUp: [UIView], notifyFromObject: Any? = nil){
self.notifyFromObject = notifyFromObject
self.viewsToPushUp = viewsToPushUp
//remember KeyboardManager itself is observing the notifications and moving the views it received from the ViewController. Hence we use self.
NotificationCenter.default.addObserver(self, selector: #selector(FooFramework.KeyboardManager.pushViewsUp), name: NSNotification.Name.UIKeyboardWillShow, object: notifyFromObject)
}
#objc public func pushViewsUp(notification: NSNotification) {
if let keyboardRectValue = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
let keyboardHeight = keyboardRectValue.height
for view in viewsToPushUp {
view.frame.origin.y -= keyboardHeight
}
}
}
}
You will also need to work with the frames properly before you get the right behavior out of this.
You should extend the lifespan of your KeyboardManagerInstance to live as long as the ViewController which has the textField is alive. You do it by declaring it as an instance variable inside the ViewController as Justin has suggested. The way you were doing it, your KeyboardManager instance is a local variable which is created and immediately released as soon as the function goes out of scope. To verify this, you can add this to your KeyboardManager class and check:
deinit {
print("KeyboardManager is perhaps dying an untimely death.")
}
Finally your ViewController class should do just this
import UIKit
import FooFramework
class ViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
var kb: KeyboardManager?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
kb = KeyboardManager(viewsToPushUp: [textField], notifyFromObject: nil)
//observe that there is no observer param in the initializer and also no "pushViewsUpWhenKeyboardWillShow" call as that behavior has already been moved to init itself.
}
}
I want to create a class to add UIButton action, and then viewcontroller can call this class to create action.
But the error message told me "unrecognized selector sent to instance "
How can I fix them?
class MYController: UIViewController{
#IBOutlet weak var pptMode: UIButton!
#IBOutlet weak var videoMode: UIButton!
#IBOutlet weak var meetingMode: UIButton!
#IBOutlet weak var videoMeeting: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
ModeChange().modeChangeUtil(self,pptMode:pptMode, videoMode: videoMode, meetingMode: meetingMode, videoMeeting: videoMeeting)
}
}
class ModeChange {
func modeChangeUtil(vc:UIViewController,pptMode:UIButton,videoMode:UIButton,meetingMode:UIButton,videoMeeting:UIButton){
pptMode.addTarget(vc, action: "modeTouched:", forControlEvents:UIControlEvents.TouchUpInside)
pptMode.tag = modeButtonTag.ppt.rawValue
videoMode.addTarget(vc, action: "modeTouched:", forControlEvents:UIControlEvents.TouchUpInside)
videoMode.tag = modeButtonTag.video.rawValue
meetingMode.addTarget(vc, action: "modeTouched:", forControlEvents:UIControlEvents.TouchUpInside)
meetingMode.tag = modeButtonTag.meeting.rawValue
videoMeeting.addTarget(vc, action:"modeTouched:", forControlEvents:UIControlEvents.TouchUpInside)
videoMeeting.tag = modeButtonTag.videoMeeting.rawValue
}
enum modeButtonTag: Int {
case ppt
case video
case meeting
case videoMeeting
}
func modeTouched(sender:UIButton){
let modeCmd:ModeCommand = ModeCommand(device: Mode(onoffUrl: OfficeConstants.MeetingRoom3A.MODE))
switch sender.tag {
case modeButtonTag.ppt.rawValue:
modeCmd.modeStatus = .PPT
case modeButtonTag.video.rawValue:
modeCmd.modeStatus = .Video
case modeButtonTag.meeting.rawValue:
modeCmd.modeStatus = .Meeting
case modeButtonTag.videoMeeting.rawValue:
modeCmd.modeStatus = .VideoMeeting
default: break
//do nothing
}
modeCmd.execute()
}
}
You passed an incorrect parameter!
The addTarget method takes 3 parameters:
target is the instance to which the selector will be sent.
action is a selector
forControlEvents is the event that you want to handle
After explaining these, can you figure out which parameter is wrong in your code?
It's the target!
Since the selector will be sent to the target, the target must have a method called modeTouched:.
In your method call, you passed in vc as the target. This means that the selector modeTouched: must be sent to vc. However, you did not define a modeTouched: method in vc! That's why the error occurred.
I guess you want to send the selector to the modeTouched: method in the ModeChange class. Thus, the target should be self:
pptMode.addTarget(self, action: #selector(modeTouched:), forControlEvents:UIControlEvents.TouchUpInside)
That should fix it!