Events from storyboard don't reach view controller - ios

(Newbie question) after mucking around with renaming folders/modules, etc. I find that events are no longer triggered. Looking in the story board inspector, all events look to be wired properly.
But no events are triggered in the view controller.
How can this be debugged? Could it be something wrong with the module names, relative paths or anything like that.
Code snippets:
class ViewController: UIViewController, STBackgroundTaskDelegate, MeshViewDelegate, UIGestureRecognizerDelegate, AVCaptureVideoDataOutputSampleBufferDelegate {
viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
// ... more here...
}
Receiving method:
#IBAction func overlayTypeChangedWithSender(_ sender: AnyObject) {
NSLog("Overlay type changed")
showOverlay()
}

You may have forgotten to change the Custom Class in Storyboard or Xcode caches outdated data. Do the following:
Copy the NameOfViewController to the Custom Class field for your viewController in Storyboard.
Clean the project (Product -> Clean)
Clear Derived Data. Here is how.
Restart Xcode
Build & Run

Have you tried connecting another control in one of your problem classes? If you can do that, have a look at the connections inspector for the new control, and earlier ones in the same view - they SHOULD be connected to the same class (of course), but if they are not, you just need to remove the old connection and reconnect

Related

What to check when outlet detection is not working?

I'm not sure what I did wrong, but I tried to connect the outlet from my main storyboard to my new ViewControllerTableViewCell.swift that I just created.
I set the class to ViewControllerTableViewCell
It contains
import UIKit
internal class ViewControllerTableViewCell : UITableViewCell {
override internal func awakeFromNib()
override internal func setSelected(_ selected: Bool, animated: Bool)
}
Now, I open up my Main.storyboard and attempting to drag my image and label
I did't see the auto detect come up.
However, when I tried to drag those into my ViewController.swift, it works.
To make sure it is not XCode Caching issue. I also restart my XCode, and still face same issue.
I was hoping to see something like this
How do I check further to make this work again?
Good question. That's one of the disadvantages of using the interface building. But anyways, here's a solution:
Make sure that your custom tableView class, ViewControllerTableViewCell is assigned to your custom cell in your storyboard, like so:
Then, click on the automatic thing above, and see if you're viewing the cell class.
Try again now to have an outlet.
Also, CMD+B (building) helps the Xcode to correct some things.
You need to assign the custom tableCell class to the cell in IB
//
//

Swift error : signal SIGABRT how to solve it

I'm just a beginner in Swift coding. My idea is quite simple which is an app with two buttons. When clicked, a textfield will change its text.
In the Main.StoryBoard, I add a textfield and two buttons.
In ViewController.swift file. I write as this:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var textfield: UITextField!
#IBOutlet weak var button: UIButton!
#IBOutlet weak var button2: UIButton!
#IBAction func action1(_ sender: UIButton) {
textfield.text="you just clicked on button1"
}
#IBAction func action2(_ sender: UIButton) {
textfield.text="you just clicked on button2"
}
}
It is supposed to be all right. However, an error appears which shows:
thread1:signal SIGABRT
in file AppDelegate.swift line:
class AppDelegate: UIResponder, UIApplicationDelegate
What is wrong with my code?
You get a SIGABRT error whenever you have a disconnected outlet. Click on your view controller in the storyboard and go to connections in the side panel (the arrow symbol). See if you have an extra outlet there, a duplicate, or an extra one that's not connected. If it's not that then maybe you haven't connected your outlets to your code correctly.
Just remember that SIGABRT happens when you are trying to call an outlet (button, view, textfield, etc) that isn't there.
For me it wasn't an outlet. I solved the problem by going to the error And reading what it said. (Also Noob..)
This was the error:
And The solution was here:
Just scroll up in the output and the error will be revealed.
To solve the problem, first clean the project and then rebuild.
To clean the project, go to MenuBar: Product -> Clean
Then to rebuild the project, just click the Run button as usual.
A common reason for this type of error is that you might have changed the name of your IBOutlet or IBAction you can simply check this by going to source code.
Click on the main.storyboard and then select open as
and then select source code
source code will open
and then check whether there is the name of the iboutlet or ibaction that you have changed , if there is then select the part and delete it and then again create iboutlet or ibaction.
This should resolve your problem
In my case I wasn't getting error just the crash in the AppDelegate and I had to uncheck the next option: OS_ACTIVITY_MODE then I could get the real crash reason in my .xib file
Hope this can help you too :)
I had the same problem. I made a button in the storyboard and connected it to the ViewController, and then later on deleted the button. So the connection was still there, but the button was not, and so I got the same error as you.
To Fix:
Go to the connection inspector (the arrow in the top right corner, in your storyboard), and delete any unused connections.
If you run into this in Xcode 10 you will have to clean before build. Or, switch to the legacy build system. File -> Workspace Settings... -> Build System: Legacy Build System.
This is a very common error and can happen for multiple reasons. The most common is when an IBOUTLET/IBACTION connected to a view controller in the storyboard is deleted from the swift file but not from the storyboard. If this is not the case, use the log in the bottom toolbar to find out what the error is and diagnose it. You can use breakpoints and debugging to aid you in finding the error.
To find out how to fix the error please use this article that I found on Google: https://rayaans.com/fixing-the-notorious-sigabrt-error-in-xcode
In my case there was no log whatsoever.
My mistake was to push a view controller in a navigation stack that was already part of the navigation stack.
Sometimes it also happens when the function need to be executed in main thread only, so you can fix it by assigning it to the main thread as follows :-
DispatchQueue.main.async{
your code here
}
For me, This error was because i had a prepare segue step that wasn't applicable to the segue that was being done.
long story:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let gosetup = segue.destination as! Update
gosetup.wherefrom = updatestring
}
This was being done to all segue when it was only for one. So i create a boolean and placed the gosetup inside it.
In my case, I was using RxSwift for performing search.
I had extensively kept using a shared instance of a particular class inside the onNext method, which probably made it inaccessible (Mutex).
Make sure that such instances are handled carefully only when absolutely necessary.
In my case, I made use of a couple of variables beforehand to safely (and sequentially) store the return values of the shared instance's methods, and reused them inside onNext block.
I had the same problem. In my case I just overwrote the file
GoogleService-Info.plist
on that path:
Platform\ios\YOUR_APP_NAME\Resources\Resources
In my case the files were present without data.
If this crash occurs when accessing a view controller within a package you may have to remove the Class and Storyboard ID from the view controller within the package and then add them again, run the project and the view controller should be found

UIKeyCommands don't work when intermediary viewController contains two viewControllers

I believe this is a non-trivial problem related to UIKeyCommands, hierarchy of ViewControllers and/or responders.
In my iOS 9.2 app I have a class named NiceViewController that defines UIKeyCommand that results in printing something to the console.
Here's NiceViewController:
class NiceViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let command = UIKeyCommand(input: "1", modifierFlags:UIKeyModifierFlags(),
action: #selector(keyPressed), discoverabilityTitle: "nice")
addKeyCommand(command)
}
func keyPressed() {
print("works")
}
}
When I add that NiceViewController as the only child to my main view controller all works correctly - pressing button "1" on external keyboard (physical keyboard when used in simulator) works like a charm. However when I add a second view controller to my main view controller the UIKeyCommands defined in NiceViewController stop working.
I'd love to understand why does it happen and how to ensure that having multiple child view controllers to my main view controller doesn't stop those child view controllers from handling UIKeyCommands.
Here is my main view controller:
class MainViewController: UIViewController {
let niceViewController = NiceViewController()
let normalViewController = UIViewController()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(niceViewController.view)
self.addChildViewController(niceViewController)
self.view.addSubview(normalViewController.view)
// removing below line makes niceViewController accept key commands - why and how to fix it?
self.addChildViewController(normalViewController)
}
}
I do not believe this is a problem with UIKeyCommands
In iOS, only one View Controller at a time may manage key commands. So with your setup, you have a container view controller with a couple child view controllers. You should tell iOS that you would like NiceViewController to have control of key commands.
Defining First Responders
At a high level, in order to support key commands, you not only must create a UIKeyCommand and add it to the view controller, but you must also enable your view controller to become a first responder so that it is able to respond to the key commands.
First, in any view controller that you would like to use key commands for, you should let iOS know that that controller is able to become a first responder:
override func canBecomeFirstResponder() -> Bool {
// some conditional logic if you wish
return true
}
Next, you need to make sure the VC actually does become the first responder. If any VCs contain some sort of text fields that become responders (or something similar), that VC will probably become the first responder on its own, but you can always call becomeFirstResponder() on NiceViewController to make it become the first responder and, among other things, respond to key commands.
Please see the docs for UIKeyCommand:
The system always has the first opportunity to handle key commands. Key commands that map to known system events (such as cut, copy and paste) are automatically routed to the appropriate responder methods. For other key commands, UIKit looks for an object in the responder chain with a key command object that matches the pressed keys. If it finds such an object, it then walks the responder chain looking for the first object that implements the corresponding action method and calls the first one it finds.
Note: While someone is interacting with the other VC and it is the first responder, NiceViewController cannot be the first responder at the same time, so you might want some key commands on the other VC as well.
Why this isn't always necessary
When only one VC is presented, iOS appears to assume that it will be the first responder, but when you have a container VC, iOS seems to treat the container as the first responder unless there is a child that says it is able to become the first responder.
Following #Matthew explanation solution is adding becomeFirstResponder() request; in viewDidAppear instead of viewDidLoad resolve my similar problem.
Swift4
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
becomeFirstResponder()
print("becomeFirstResponder?: \(isFirstResponder)")
}
I found while experimenting with this that if you manually call becomesFirstResponder() on the child view controllers, it allows you to have multiple first responders and all key commands show up when hitting command.
I'm not sure why this works exactly as surely you're only supposed to have a single firstResponder at any one time.

Xcode 6 interface builder does not show custom class

I have an error in my Xcode 6.3 interface builder.
When i create a new custom class and want to add it to interface builder custom class field. It is not available. I do use Swift as languag.
What I tried:
delete derived data
reinstall Xcode
cleand project
created new project
Class name is same as file name
Superclass and interface builder class are the same
Nothing worked out ;-( Any idea what it could be ?
I had the same issue and thought it as a bug. But it was my misunderstanding.
What actually happens is:
Custom Class drop down list shows only those custom classes which are subclasses of currently selected object's class.
For example, if we have imageview object in xib and after selecting it, when we see custom class drop down list, it'll show only custom classes inherited from (subclass of) UIImageView.
What I was doing wrong was looking for those custom classses which are subclasses of UIView.
In your case, it might be, not 100% sure, happening due to your TimerCVC is a subclass of UICollectionViewController instead of UIViewController.
TimerCVC is not a subclass of UIViewController
Press ctrl-n -> on the left panel choose iOS ->Source -> Cocoa Touch Class -> from the dropdown menu choose UIViewController -> And then fill the name field (automatically Xcode autocomplete with ViewController on the end).
It should look like this
import UIKit
class TimerViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

What would be better strategy for IBActions in protocols in Swift?

I am creating a Swift project and I want to define a specific protocol that enforces other components to implement a animate method:
protocol AnimatableBehavior {
#IBAction func animate()
}
The problem is I want this method to be an IBAction, but I get this error from XCode:
Only instance methods can be declared 'IBAction'
My question is, how would you implement such a thing?
I have considered:
Remove #IBAction, but then I need to remember adding it in every class that implements. Not very elegant and error prone.
Create a base class instead of protocol, but then I am enforcing all components to subclass my base class instead of their own choice ones, so it is not a valid option.
Any other ideas?
EDIT: Response to comments below.
The idea of the IBAction on the protocol is because in the project there will be many different devs implementing small UI components, all of which have the animate method. The components can be added programatically or by Interface Builder and it is very convenient that they are always IBAction because I plan to compose them from IB files to simplify the View Controllers to the maximum extent (and this is clearly a View only task).
Therefore, the solution proposed below of adding a method in the controller that just calls the animate of the component is not good because it is redundant code and makes your Controller more dependent on your View.
The idea of letting the dev to remember adding the IBAction keyword on the method is workable, but as I said it is error prone (and by that I mean that there will be some forgetting about it), and I want to make sure that this is always accessible from IB. It also adds extra cognitive load, because I will need to document this lack of IBAction on the protocol and request the implementor to add it manually.
I know is not the common way of working in iOS and UIKit, but that was why I posted the question, maybe someone has an alternative idea.
It doesn't make any sense to have an #IBAction in a protocol. #IBAction is nothing more than a keyword for Interface Builder to have a hook when you're control+dragging from Interface Builder to your actual source code.
This is just a simple misunderstanding of what #IBAction actually is and does.
A method does not have to be marked as #IBAction in order for it to be the target of a UI element's actions. You programmatically hook up any method to any action using the addTarget set of methods that UI elements have. The method does not have to be marked as an #IBAction to do this.
Regardless of whether or not a protocol defines a method as #IBAction, the class conforming to the protocol can add it (and still be conforming to the protocol.
protocol FooProtocol {
func doSomething()
}
class ViewControllerA: UIViewController, FooProtocol {
#IBAction func doSomething() {
// do something
}
}
class ViewControllerB: UIViewController, FooProtocol {
func doSomething() {
// do something
}
}
Both of these view controller subclasses conform to the protocol, and having #IBAction there is ONLY necessary if you intend to hook up an action from interface builder!
Ultimately, whatever you're trying to do, if you think an #IBAction is necessary in your protocol, I think you're taking the wrong approach to something. It's hard to say what the right approach would be without knowing more details about what you're actually doing, but it never makes sense for #IBAction to belong in a protocol.
To me, it seems like the methods your protocol enforces shouldn't at all be tied to #IBAction methods. Instead, whatever user interaction should trigger the animation, should in turn call the animate method. For example, if we weren't talking about the protocol, my recommendation would be this sort of set up:
class ViewController: UIViewController {
#IBAction func buttonThatStartsAnimation {
self.animate()
}
func animate {
// code that does all the animation
}
}
So, with the protocol, we should take the same seperation of duties between the method that's actually initiating the animation code (which in the case of protocols, this is obviously some other outside class), and the animate method should only ever handle doing the relevant animations.
Importantly, just as a general rule, you shouldn't be directly referring to your #IBAction methods or your #IBOutlet variables directly from outside the class which defines them.
I totally agree with OP, although until Swift 3.1 you can't really declare anything as #IBOutlet, #IBAction, #objc etc in a protocol. As a workaround, I chose to build something based on pod 'ActionKit' and wrote something like:
protocol RequiresAnimation {
var animateButton: UIButton! { get }
func enableAnimateButton()
func actionAnimate()
}
extension RequiresAnimation where Self: UIViewController {
func enableAnimateButton() {
animateButton.addControlEvent(.touchUpInside) {
self.actionAnimate()
}
}
func actionAnimate() {
// animate here
}
}
And make your view controller:
class MyViewController: UIViewController, RequiresAnimation {
#IBOutlet var animateButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
enableAnimateButton()
}
}
I wish there would be any easier approach, but so far you make need to do these 2 things manually: declaring your button as #IBOutlet and call a setup function. The reason why we need to import ActionKit is that we can't addTarget in protocol extension.

Resources