I am new to Swift so this will hopefully be something simple but nothing that I have found has helped.
I am using SWRevealViewController to add a side menu onto my app, it basically creates a table view controller onto the side of the regular view controller. When the user taps on one of the cells I want to be able to call a function that is in the regular view controller.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
ViewController().testFunction()
}
This then calls the function:
func testFunction() {
button1.setTitle("TEST", for: .normal)
}
Now, the app calls this function correctly but I am left with the error
fatal error: unexpectedly found nil while unwrapping an Optional value
If I ask the function to Print("test") instead of changing the button title, that works absolutely fine. When I try calling the function from the same regular view controller there are no errors and the button changes text.
The cause for crash is this line:
ViewController().testFunction()
What you do is you create a brand new instance of ViewController and call a method that does something with one of its properties. Now, we can safely assume that your button1 is a button you created in Interface Builder - therefore it doesn't exist by the time you are trying to access it.
What you should be doing is call this method on some already existing instance of ViewController, not creating a brand new instance of it (which doesn't make much sense anyway - it's not in the navigation stack, it just exists locally in your didSelectRowAt)
Related
I'm developing an iOS app which is just for debugging some other work I'm involved in. I've ran out of space on the first view controller (lots of buttons and images) and want to expand into a second view controller. I'm implemented a segue between the views and use the following code in the second viewcontroller to trigger functions in the first view controller:
#IBAction func imagePreset_1_clicked(_ sender: Any) {
firstViewController().functionX()
}
This works but if functionX has the following code:
DispatchQueue.main.async { () -> Void in
self.source.text = "hello"
}
which is a label on firstViewController, then I get the following error:
fatal error: unexpectedly found nil while unwrapping an Optional value
I'm new to Swift. Am I correctly creating a reference to the firstViewController or am I creating a new instance of it? It the problem that the label being updated is not in "scope"?
All I want is extra space for icons. Is there a better way to achieve this?
Thanks
update
So I've tried a scrolling view and this works but can anyone advise how to do what I'm trying to achieve with a second view controller accessing the functions in the first view controller? I think that's a nicer ux experience. Is it any easier using a tab bar?
Have you considered using a single ViewController with scrollable view inside ?
Using this, you'll be able to display as many content as you'd like :)
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.
I'm building an app that is a list of homework.
I'm a sort of beginner so I'm not using custom classes to save it and neither using CoreData. Just using NSUserDefaults and passing the data inside arrays between the views. My project is almost done (I want to implement some stuff like animations and etc but first I need to get it fully working right?), but I have 2 silly problems to solve, but I can't do it alone!
If I was going to put the code here the question would be too long, so I will leave the link of the project for you who is helping me check out.
https://github.com/HenriqueDoura/Agenda Here it is!
So, the first problem:
as it is a homework list app, it will control if the app isPendent or if it !isPendent, for that, I've put a label to the custom AtivCell called pendenciaLbl (which means pendencyLbl in English). By default, the bool is initialized as true, cause if the user is adding a homework, it is obvious that he did not finish it yet, right?
To change the bool value to false I've added a new actionForRowAtIndexPath action, called Feito (which means Done in English). By pressing it, I make the isPendent value false. On the table view func
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
I've put an if/else statement:
if isPendent {
cell.pendenciaLbl.text = "Pendente"
cell.pendenciaLbl.textColor = UIColor.redColor()
} else {
cell.pendenciaLbl.text = "Concluída"
cell.pendenciaLbl.textColor = UIColor.greenColor()
}
Pendente means Pendent and Concluída means concluded.
The thing is that when I set the cell I'm on to Feita/Done, it is setting all the other cells as done too and changing all the pendenciaLbl.text to Concluded, which is obviously not what I want, I just want to set ONE cell as Done.
The second and last problem envolves the same boolean isPendent. I don't know how to save this value, cause if I set a cell as Done and relaunch the app (after killing it via the app switcher) the pendenciaLbl comes back to "Pendent". How can I save the value when a cell is set to "Concluded" and check if it was set before when I launch the app?
Sorry for all english mistakes, I'm Brazillian.
Right now you only have one variable for isPendent. So if one cell changes that variable, all the cells are changed.
What you'll need to do is create a Array of Bools, one for each cell. Just like you have arrays of strings for other data like material and describes.
var pendent = [Bool]()
Then in cellForRowAtIndexPath
if pendent[indexPath.row] {
cell.pendenciaLbl.text = "Pendente"
cell.pendenciaLbl.textColor = UIColor.redColor()
} else {
cell.pendenciaLbl.text = "Concluída"
cell.pendenciaLbl.textColor = UIColor.greenColor()
}
I am new to iOS /swift programming and I am working on an app developed by someone else, fixing some bugs.
The app is essentially a music player and the music has to be played also in background, giving the possibility to play/pause/skip from the lock screen. The app has several views, one of them, the main one, contains all the code related to the player itself (player.swift), the other ones containing other additional pages/features.
The commands from the lock screen works only when I lock the screen starting from the main view, if I do it starting from another view (e.g. the help view, which is just a page which is displayed over the player when the help link is tapped) they don't work. Reading several articles here I've realized that the reason is that the related code is in player.swift:
override func remoteControlReceivedWithEvent(event: UIEvent) {
if (event.type == UIEventType.RemoteControl) {
switch (event.subtype) {
case UIEventSubtype.RemoteControlPlay:
self.onPlayPause(self);
case UIEventSubtype.RemoteControlPause:
self.onPlayPause(self);
case UIEventSubtype.RemoteControlTogglePlayPause:
self.onPlayPause(self);
case UIEventSubtype.RemoteControlNextTrack:
onNext(nil)
default:
break
}
}
}
so I have understood the problem, but even if I've read several related articles (including remoteControlReceivedWithEvent called on iOS 7.0 device but not iOS 8.0, Using lock screen for my app?, Swift. Receive remote control events to work with MPNowPLayingInfoCenter) I can't figure out where do I need to move this code and if I need to move something else or make modifications.
EDIT. I moved the code in AppDelegate.swift (deleting the code in player.swift) , as suggested. It seems it now intercepts commands even if I lock the device from a view different than player.swift. I have two problems, though:
1) It seems it works just once, If I click on "next" from the lock screen I can see from a debug string that the command is intercepted, If I do it a second time nothing happens
2) I need to call the methods (onPlayPause and onNext) in player.swift from AppDelegate.swift, I guess those methods expect to have a player object set and/or they refer to variables declared in player.swift and I don't know how to handle this. For example the onNext method is declared as
#IBAction func onNext(sender: AnyObject?) {
oldImage = iAlbumArt.image
.......
and if I call the method as a new instance from AppDelegate
player().onNext(nil)
I get an error because iAlbumArt.image is NIL. iAlbumArt is a variable declared in the Player class as
#IBOutlet weak var iAlbumArt: UIImageView!
Sorry for the naive questions but I've been looking into iOS development just since a couple of weeks ago.
Try adding it to your App Delegate class.
Edit:
To forward remote control events to the view controller, add this code to the app delegate (assuming your player view controller is called PlayerViewController):
let vcs = (self.window!.rootViewController as! UINavigationController).viewControllers
let indexOfPlayer = (vcs as! NSArray).indexOfObjectPassingTest { (vc, idx, stop) in
return (vc.isKindOfClass(PlayerViewController))
}
let playerVC = vcs[indexOfPlayer];
Edit 2:
Place the override func remoteControlReceivedWithEvent method in your App Delegate Class.
At the top of that method, place the code snippet shown above.
In that method, replace self with playerVC.
In your player view controller, add code to respond to onPlayPause and onNext functions.
Note:
The reason why this code:
player().onNext(nil)
was throwing an error was because player() creates a brand-new instance of your player class. You want to use the existing instance so the changes get reflected on the screen.
I'm building an app for iOS using the Swift language. I start with a table view controller as my root view controller, and then I have a secondary view controller in which a variable (passData) is defined. This all works fine, and it passes the data correctly (I think) from the secondary view controller back to the primary view controller. However, when the user returns back to the primary view controller, I need a function to execute which will then add the 'addTitle' value to an array. I know how to add it to the array, but...
I don't know how to initiate the function when the view is returned to. What I mean is, after the user is finished on the secondary view controller AND the variable "passData" is defined, they will then push the back button on the navigation bar. I then need the primary view controller to recognise that it is once again being displayed to the user, and then execute the following code:
tableData += [passData]
tableSubtitle += [passDescription]
I have tried the following:
override func viewDidAppear() {
tableData += [passData]
tableSubtitle += [passDescription]
}
But this gives the error as Method does not override any method from its superclass.
Essentially, I just need to know how to start a function when the view displays. How can i achieve this?
you need to call super.viewDidAppear(animated) and the method signature takes a Bool so you should say:
override func viewDidAppear(animated: Bool)
ProTip: If you want to override a method you can just start typing the method name you want to overload and Xcode will auto suggest the method name and fill in the override declarative. So on a new line start typing viewDid and you should see the viewDidAppear method in the autocompletion drop down. Pressing enter will complete the method signature for you.