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.
Related
I am building a Cocoa app for production and when I create an NSViewController for routing without NSStoryboard instantiate, I got the error like below.
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[NSNib _initWithNibNamed:bundle:options:] could not load the nibName: ContentFully.AnotherController in bundle (null).
Actually I solved my problem via using an NSViewController to adding NSStoryboard but I would like to learn what is going on when I call it programmatically and why did crash?
Working scenario
let storyboard = NSStoryboard(name: "Dashboard", bundle: nil)
let vc = storyboard.instantiateInitialController() as! AnotherController
Crashed scenario
let vc = AnotherController()
self.view.window?.contentViewController = vc
Even if I create a class fully programmatically and non-relational with NSStoryboard I could not use it when I change the contentViewController. Is it necessary to search every controller in NSBundle for Swift?
Thanks in advance
A very simple working version would be:
import Cocoa
class AnotherController: NSViewController {
override func loadView() {
view = NSView(frame: NSMakeRect(0.0, 0.0, 400.0, 270.0))
let label = NSTextField(labelWithString: "Another Controller")
view.addSubview(label)
}
}
I can call that from my first controller using the code you say crashes and get the expected result.
#IBAction func replaceMe(_ sender: Any) {
let vc = AnotherController()
self.view.window?.contentViewController = vc
}
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)
}
}
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.
In my project I have few view controllers which are subclasses of UITableViewController, UIViewController, on each I want to implement this behavior:
When user taps outside of a text field it should dismiss the keyboard which was visible when user tapped inside it.
I can easily implement it by defining a tap gesture recognizer and associating a selector to dismiss the keyboard:
class MyViewController {
override func viewDidLoad() {
super.viewDidLoad()
configureToDismissKeyboard()
}
private func configureToDismissKeyboard() {
let tapGesture = UITapGestureRecognizer(target: self, action: "hideKeyboard")
tapGesture.cancelsTouchesInView = true
form.addGestureRecognizer(tapGesture)
}
func hideKeyboard() {
form.endEditing(true)
}
}
Since I have to implement same behavior in multiple view controllers, I am trying to identify a way to avoid using repetitive code in multiple classes.
One option for me is to define a BaseViewController, which is subclass of UIViewController, with all above methods defined within it and then subclass each of my view controller to BaseViewController. The problem with this approach is that I need to define two BaseViewControllers one for UIViewController and one for UITableViewController since I am using subclasses of both.
The other option which I am trying to use is - Protocol-Oriented Programming. So I defined a protocol:
protocol DismissKeyboardOnOutsideTap {
var backgroundView: UIView! { get }
func configureToDismissKeyboard()
func hideKeyboard()
}
Then defined its extension:
extension DismissKeyboardOnOutsideTap {
func configureToDismissKeyboard() {
if let this = self as? AnyObject {
let tapGesture = UITapGestureRecognizer(target: this, action: "hideKeyboard")
tapGesture.cancelsTouchesInView = true
backgroundView.addGestureRecognizer(tapGesture)
}
}
func hideKeyboard() {
backgroundView.endEditing(true)
}
}
In my view controller I confirmed to the protocol:
class MyViewController: UITableViewController, DismissKeyboardOnOutsideTap {
var backgroundView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// configuring background view to dismiss keyboard on outside tap
backgroundView = self.tableView
configureToDismissKeyboard()
}
}
Problem is - above code is crashing with exception:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MyProject.MyViewController hideKeyboard]: unrecognized selector sent to instance 0x7f88c1e5d700'
To avoid this crash I need to redefine hideKeyboard function within MyViewControllerclass, which is defeating my purpose of avoiding repetitive code :(
Please suggest if I am doing any thing wrong over here or is there any better way to implement my requirement.
I think there are two possible problems: casting Self to AnyObject, and not using the new #selector syntax.
Instead of casting Self to AnyObject, define the protocol as a class-only protocol:
protocol DismissKeyboardOnOutsideTap: class {
// protocol definitions...
}
Then use type constraints to apply your extension to only subclasses of UIViewController, and use Self directly in your code, rather than casting to AnyObject:
extension DismissKeyboardOnOutsideTap where Self: UIViewController {
func configureToDismissKeyboard() {
let gesture = UITapGestureRecognizer(target: self,
action: #selector(Self.hideKeyboard()))
gesture.cancelsTouchesInView = true
backgroundView.addGestureRecognizer(gesture)
}
}
Edit: I remembered the other problem I ran into when doing this. The action argument for UITapGestureRecognizer is an Objective-C selector, but Swift extensions to classes aren't Objective-C. So I changed the protocol to an #objc protocol, but that was a problem because my protocol included some Swift optionals, and it also introduced new crashes when I tried to implement the protocol in my VC.
Ultimately, I discovered an alternative method that didn't require an Objective-C selector as an argument; in my case, I was setting an NSNotification center observer.
In your case you might be better off simply extending UIViewController, as UITableViewController is a subclass, and subclasses inherit extensions (I think).
As pointed out by the user ConfusedByCode, even though a protocol oriented approach starts out as a nice one, it becomes un-Swifty as the compiler forces you to use the keyword #objc.
Therefore extending UIViewController is a better approach; at least in my opinion.
In order to maintain a clean project structure, create a file named UIViewController+DismissKeyboard.swift and paste the following content inside:
import UIKit
extension UIViewController {
func configureKeyboardDismissOnTap() {
let keyboardDismissGesture = UITapGestureRecognizer(target: self,
action: #selector(self.dismissKeyboard))
view.addGestureRecognizer(keyboardDismissGesture)
}
func dismissKeyboard() {
// to be implemented inside your view controller(s) wanting to be able to dismiss the keyboard via tap gesture
}
}
Afterwards, any one of your view controllers or other base classes from Apple inheriting from UIViewController such as UITableViewController, etc. for that matter, will have access to the method configureKeyboardDismissOnSwipeDown().
Therefore, merely calling configureKeyboardDismissOnSwipeDown() inside viewDidLoad in each of your view controllers will be automatically injecting a swipe down gesture to dismiss the keyboard.
One caveat still remaining is that, every view controller will be in need to call configureKeyboardDismissOnSwipeDown() separately. Unfortunately, this is a bummer as you can't simply override viewDidLoad() in your extension. Moreover, it's still a mystery to me as to why Apple haven't implemented this directly into the keyboard so that us developers would not need to code around it.
Anyways, this issue can be solved by a technique called Method Swizzling. Basically, it's overriding methods given by Apple so that their behaviour change at runtime. I won't go into any more detail about method swizzling any more than saying that it can be highly dangerous to play around as you would be modifying battle-tested, solid code provided by Apple and used by the system.
Afterwards, when you implement the above provided dismissKeyboard() method in a view controller where you want to be able to dismiss the keyboard, you'll be able to do so.
TapGestureDismissable.swift
#objc protocol TapGestureDismissable where Self: UIViewController {
func hideKeyboard()
}
extension TapGestureDismissable {
func configureTapGestureToDismissKeyboard() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(hideKeyboard))
tapGesture.cancelsTouchesInView = true
view.addGestureRecognizer(tapGesture)
}
}
Inside your ViewController
extension myViewController: TapGestureDismissable {
func hideKeyboard() {
view.endEditing(true)
}
}
I am working on a project with Swift and Storyboards. It's a conversion project from a traditional IB and Objective-C project. I am having an issue with a UITableView instantiating when the view is loaded. Let me explain.
The project is a navigation project. Here is an overview of the Storyboard.
The Storyboard's first viewController is HomeViewController and is a landing page that displays general info. The next VC is called FeedViewController shows a number of RSS feeds. You can see an expanded screen shot of the NavigationController, HomeViewController and FeedViewController in the picture below.
My problem is that I can't get the tableView to Instantiate. I first checked to make sure that my tableView was connected as an outlet and that the dataSource and delegate properties were connected. You can see this in the pic below.
In my FeedViewController class I have an Outler property called feedsTableView. You can see the declaration in the code below.
class FeedViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, FLODataHandlerDelegate
{
// View Contoller and Protocol Properties
var floView : FLOViewController?
var dataHandler : FLODataHandler?
// Interface and Content Properties
var refreshControl : UIRefreshControl?
// IBOutlets
#IBOutlet weak var feedsTableView: UITableView!
#IBOutlet weak var backgroundImage: UIImageView!
In the HomeViewController I have a FeedViewController property that I intend to use to gain access to FeedViewController's feedsTableView.
class HomeViewController: UIViewController, FLODataHandlerDelegate, MFMailComposeViewControllerDelegate
{
// View Contoller and Protocol Properties
var feedViewController : FeedViewController?
var dataHandler : FLODataHandler?
When HomeViewController's viewDidLoad() method is called I start the dataHandler - which instantiates the FeedViewController - and set it to my FeedViewController property.
override func viewDidLoad()
{
super.viewDidLoad()
// Set up the gesture recognizer to allow for swiping to the feed VC.
let recognizer = UISwipeGestureRecognizer(target: self, action: Selector("goToNext"))
recognizer.direction = UISwipeGestureRecognizerDirection.Left
self.view.addGestureRecognizer(recognizer)
// Start the data handler
self.setUpDataHandler()
}
setUpDataHandler()
func setUpDataHandler()
{
// Intitalize FeedVC for use later in the VC
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("FeedViewController") as! FeedViewController
self.feedViewController = vc
}
I also have a fail safe that if someone were to go to the FeedViewController before the setUpDataHandler() method is called then I instantiate FeedViewController here as well.
func goToNext()
{
// Grab the feedViewController so it can be pushed onto the stack. Make sure you set up the storyboard identifier.
let feedVC = self.storyboard!.instantiateViewControllerWithIdentifier("FeedViewController") as! FeedViewController
self.feedViewController = feedVC
self.navigationController!.pushViewController(self.feedViewController!, animated: true)
}
However the feedsTableView is not getting instantiated. In the viewDidLoad() method of FeedViewController I attempt to add the feedsTableView to a UIRefreshController.
override func viewDidLoad()
{
super.viewDidLoad()
self.refreshControl = UIRefreshControl()
self.refreshControl!.addTarget(self, action: "refreshInvoked:state:", forControlEvents: UIControlEvents.ValueChanged)
// See the note in viewDidLoad in FLOViewController.
self.feedsTableView.addSubview(self.refreshControl!)
}
When the app runs I get the following error.
fatal error: unexpectedly found nil while unwrapping an Optional value
The image below shows were this is called. It's the viewDidLoad() of the FeedViewController. As you can see in the picture I even tried instantiating the feedsTableView before adding it to the UIRefreshController and I still get the error.
Any help would be greatly appreciated.
Take care,
Jon
The reason why it doesn't work in the very last case, where you manually instantiate UITableView and assign that to self.feedsTableView, is that self.feedsTableView is declared weak. Thus, the table view comes into existence, is assigned, and vanishes in a puff of smoke because it has no memory management. By the time you get to the last line, self.feedsTableView is nil once again.
Thus, the solution for that last case is to remove the weak designation from your feedsTableView declaration.
That will get you past the crash in that last case. But of course you won't see anything because you are not also inserting the table view into your interface.