I am trying to create a simple meditation app where when I click a button with time - it takes me to a new screen where you can see the timer. In my code everything works fine but the time label shows on the the new screen one step behind. So when I click the 1st button I see nothing on the new screen, then I click 2nd button and I see the time from the 1st button.
The 1st screen:
import UIKit
class MeditationController: UIViewController {
var minutes: String?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func minPressed(_ sender: UIButton) {
self.performSegue(withIdentifier: "goToTimer", sender: self)
minutes = sender.currentTitle!
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToTimer" {
let destinationVC = segue.destination as! TimerViewController
destinationVC.timer = minutes
}
}
}
The 2nd screen:
import UIKit
class TimerViewController: UIViewController {
var timer: String?
#IBOutlet weak var timerLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
timerLabel.text = timer
}
}
I am new to programming and I am sure this issue is really easy to solve but I've spent a few hours and I just can't seem to get how to solve this.
Thank you :)
App View
Related
Struggling to get my viewControllers to send value from the main viewController to a second. I want it to happen on a button click, I'm going to get the value from the button and pass it to the new form. But it just isn't working.
Code for main ViewController
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func butClick(_ sender: UIButton) {
NSLog("Button Pressed : %#",[sender .currentTitle])
//var tt = [sender .currentTitle]
// Create the view controller
let vc = TimesTablesViewController(nibName: "TimesTablesViewController", bundle: nil)
vc.passedValue = "xx"
self.performSegue(withIdentifier: "pushSegue", sender: nil)
}
}
Code for second viewController called TimesTablesViewController:
class TimesTablesViewController: UIViewController {
#IBOutlet weak var titleLabel: UILabel!
var passedValue:String = ""
override func viewDidLoad() {
super.viewDidLoad()
titleLabel?.text = "\(passedValue) Times Table"
}
}
I've followed tutorials but can't seem to solve the problem! Thanks for any help!
Replace
self.performSegue(withIdentifier: "pushSegue", sender: nil)
with
self.present(vc,animated:true,completion:nil)
or ( if the current vc is inside a naigation )
self.navigationController?.pushViewController(vc,animated:true)
Using
self.performSegue(withIdentifier: "pushSegue", sender: nil)
is fit with storyboards not xibs and if this your case then you need to use the above line only inside the button action with implementing this method inside the source vc
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "pushSegue" {
if let nextViewController = segue.destination as? TimesTablesViewController{
nextViewController.passedValue = "xx"
}
}
}
I’m assuming that the new view controller is appearing, but you’re simply not seeing the data. If so, you’re evidently using storyboards. The TimesTablesViewController(nibName:bundle:) only works if you’re using XIB/NIBs and manually presenting new view controller.
If you’re really using storyboards, simplify your butClick method:
#IBAction func butClick(_ sender: UIButton) {
NSLog("Button Pressed")
performSegue(withIdentifier: "pushSegue", sender: self)
}
But implement prepare(for:sender:):
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? TimesTablesViewController {
destination.passedValue = "xx"
}
}
Assuming the above fixes your problem, I might suggest a further simplification. Notably, if your butClick(_:) method is really only calling performSegue, you can segue to this next scene without any #IBAction method at all:
remove butClick(_:) entirely;
remove the connection between the button and the butClick method in IB, on the “Connections Inspector” tab in the right panel; and
control-drag from the button previously hooked up to butClick(_:) to the scene for TimesTablesViewController.
That will simplify your code further.
I have read through a lot of different posts regarding this issue, but none of the solutions seemed to work for me.
I started a new app and I placed the initial ViewController inside a navigation controller. I created a second view and linked them together on the storyboard with a segue. The segue works successfully, and I can see the data I am transferring in a print statement from the second screen, but the screen shows black.
WelcomeScreen:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "segueToTraits"{
if let gender = self.selectedGender{
let traitVC = segue.destinationViewController as? TraitViewController
traitVC!.gender = gender
}
}
}
func sendGenderToTraitsView(gender : String?){
performSegueWithIdentifier("segueToTraits", sender: self)
}
#IBAction func button1(sender: UIButton) {
selectedGender = boyGender
self.sendGenderToTraitsView(selectedGender)
}
#IBAction func button2(sender: UIButton) {
selectedGender = girlGender
self.sendGenderToTraitsView(selectedGender)
}
Storyboard:
Link to image of my storyboard
My segue is set as follows:
Link to image of my segue information
Also, my viewControllers are named WelcomeViewController and TraitViewController. They have storyboard id's of welcomeVC and traitsVC.
Any help would be incredibly appreciated. Let me know if you need any other information.
By following the steps that #ronatory had laid out so well for me in the accepted answer, I was able to see that I had set up my TraitViewController as a UIPageViewController instead of UIViewController.. and so it didn't generate any errors, but it just took me to a black screen. Feel silly that I read through my code so many times and never noticed this.
Main point: If you're getting a black screen on a ViewController randomly, make sure your class is extending the correct parent class.
in my case:
class TraitViewController: UIPageViewController {
needed to be
class TraitViewController: UIViewController {
I've build your app and everything works, maybe you've missed something, here is my solution (Note: Code is in Swift 3.0, but should be easy to adopt it to Swift 2.*):
The storyboard:
Set the segueToTraits identifier:
Set the TraitViewController class as custom class in the storyboard:
The view controller with the buttons:
import UIKit
class ViewController: UIViewController {
let boyGender = "boy"
let girlGender = "girl"
var selectedGender: String?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueToTraits"{
if let gender = self.selectedGender {
let traitVC = segue.destination as? TraitViewController
traitVC!.gender = gender
}
}
}
func sendGenderToTraitsView(gender : String?){
performSegue(withIdentifier: "segueToTraits", sender: self)
}
#IBAction func button1(sender: UIButton) {
selectedGender = boyGender
self.sendGenderToTraitsView(gender: selectedGender)
}
#IBAction func button2(sender: UIButton) {
selectedGender = girlGender
self.sendGenderToTraitsView(gender: selectedGender)
}
}
The trait view controller:
import UIKit
class TraitViewController: UIViewController {
var gender: String = ""
override func viewDidLoad() {
super.viewDidLoad()
print("gender: \(gender)")
}
}
Result:
You can find the sample project here
I'm developing an app for iPad Pro. In this app, containerView use to add additional views and interact with them.
First, I created a protocol:
protocol DataViewDelegate {
func setTouch(touch: Bool)
}
Then, I created my first view controller
import UIKit
class ViewController: UIViewController, DataViewDelegate {
#IBOutlet var container: UIView!
#IBOutlet var labelText: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
func setTouch(touch: Bool) {
if touch == true {
labelText.text = "Touch!"
}
}
}
And finally, I created a view that will be embedded in containerView.
import UIKit
class ContainerViewController: UIViewController {
var dataViewDelegate: DataViewDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func touchMe(sender: AnyObject) {
dataViewDelegate?. setTouch(true)
}
}
But for some reason, nothing happened, the first view controller receives nothing in setTouch function.
My question is: In this case, using container, how can I make the communication between two ViewsControllers?
Like #nwales said you haven't yet set the delegate. You should do set the delegate in prepareForSegue function on your first viewController (who contain the viewContainer)
First select the embed segue and set an identifier in the attributes inspector.
Then in the parentViewController implement the func prepareForSegue like this:
Swift 4+:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "the identifier") {
let embedVC = segue.destination as! ViewController
embedVC.delegate = self
}
}
Below:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
if (segue.identifier == "the identifier") {
let embedVC = segue.destinationViewController as! ContainerViewController
embedVC.dataViewDelegate = self
}
}
Looks like you defined the delegate, but have not set the delegate. This happens to me all the time.
I am making a game for iOS with SpriteKit.
I have 2 Viewcontrollers. One is the GameViewController and the other one is the MenuViewController. Let's call them A and B respectively.
When the player dies, a function is called in GameScene.swift that launches a modal "Lost" Segue to B. There, the player can restart the game or buy a life and a "Back" Segue is called to A.
I need to dismiss the additional Views that get created each time I call a segue.
Problem is: I need the "Lost" Segue to send data about the Score to View B and I need the "Back" Segue to send data to View A about wether or not the player used a life.
I have implemented all this. But now I need to find how to dismiss old views that keep eating the device's memory, thus leading to lag and crash.
I have googled for hours and hours. No solution was adapted to my situation.
The solutions I found either caused my app to bug, data not to be passed or views not to be generated.
I will not add code here since there is a LOT. But I am sure the answer is actually really easy, just not for a beginner like me.
I think a possible solution would be an unwind segue from B to A ?
But do unwind segues pass data along ?
Moreover, I found no answer I could understand on how to use an unwind segue.
I exhausted all my possibilities. Stack Exchange is my last chance.
You definitely should use an unwind segue to return to the previous viewController, otherwise as you have found your memory usage increases until your apps quits.
I created the following example from your description. It uses a standard segue to move from the GameViewController to the MenuViewController and it uses an unwind segue to move from the MenuViewController back to the GameViewController.
The GameViewController has a Player Dies UIButton, a UITextField for entering a score, and a UILabel for displaying the lives.
The MenuViewController has a UILabel for showing the score, a Buy a Life UIButton for adding lives, and a Restart UIButton for returning to the GameViewController.
Here's the code:
GameViewController.swift
import UIKit
class GameViewController: UIViewController {
#IBOutlet weak var scoreTextField: UITextField!
#IBOutlet weak var livesLabel: UILabel!
var lives = 3
func updateLivesLabel() {
livesLabel.text = "Lives: \(lives)"
}
override func viewDidLoad() {
super.viewDidLoad()
updateLivesLabel()
}
// This is the function that the unwind segue returns to.
// You can call it anything you want, but it has to be in
// the viewController you are returning to, it must be tagged
// with #IBAction and it must take a UIStoryboardSegue as its
// only parameter.
#IBAction func returnFromMenu(segue: UIStoryboardSegue) {
print("We're back in GameViewController")
// Update the lives label based upon the value passed in
// prepareForSegue from the MenuViewController.
updateLivesLabel()
}
#IBAction func goPlayerDies(sender: UIButton) {
lives--
self.performSegueWithIdentifier("Lost", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Lost" {
let destinationVC = segue.destinationViewController as! MenuViewController
destinationVC.score = Int(scoreTextField.text ?? "") ?? 0
destinationVC.lives = lives
}
}
}
MenuViewController.swift
import UIKit
class MenuViewController: UIViewController {
#IBOutlet weak var scoreLabel: UILabel!
var score = 0
var lives = 0
override func viewDidLoad() {
super.viewDidLoad()
scoreLabel.text = "Score: \(score)"
}
#IBAction func buyLife(sender: UIButton) {
lives++
}
#IBAction func goRestart(sender: UIButton) {
self.performSegueWithIdentifier("Back", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Back" {
let destinationVC = segue.destinationViewController as! GameViewController
destinationVC.lives = lives
}
}
}
This is how you wire up the forward segue to be called programmatically:
Control-drag from ViewController icon to the MenuViewController:
Select Present Modally from the pop-up:
Click on the segue arrow between the viewControllers and give it an identifier in the Attributes Inspector:
This is how you wire up the unwind segue to be called programmatically:
Control-drag from ViewController icon to Exit icon:
Choose returnFromMenu from pop-up:
Click on the Unwind Segue in the Document Outline and give it the identifier "Back" in the Attributes Inspector on the right:
Alternate Answer
Instead of using segues, you can present and dismiss viewControllers manually. The advantage for your app is that the MenuViewController will be allocated only once and will persist for the life of the app. This same viewController will be presented and dismissed repeatedly, but it will not be deallocated which I suspect is leading to your crashes.
The GameViewController will be the initialViewController that is created by the Storyboard. The MenuViewController will be loaded in viewDidLoad of the GameViewController.
To make this work, you need to add an identifier to the MenuViewController so that it can be instantiated by name. Click on the MenuViewController in the Storyboard and set its Storyboard ID in the Identity Inspector:
Here is the code. Note that all mention of segues is gone. Note how viewWillAppear is used to update the viewControllers.
GameViewController.swift
import UIKit
class GameViewController: UIViewController {
#IBOutlet weak var scoreTextField: UITextField!
#IBOutlet weak var livesLabel: UILabel!
var menuViewController: MenuViewController?
var lives = 3
func updateLivesLabel() {
livesLabel.text = "Lives: \(lives)"
}
override func viewDidLoad() {
super.viewDidLoad()
menuViewController = self.storyboard!.instantiateViewControllerWithIdentifier("MenuViewController") as? MenuViewController
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
updateLivesLabel()
}
#IBAction func goPlayerDies(sender: UIButton) {
lives--
menuViewController?.score = Int(scoreTextField.text ?? "") ?? 0
menuViewController?.lives = lives
self.presentViewController(menuViewController!, animated: true, completion: nil)
}
}
MenuViewController.swift
import UIKit
class MenuViewController: UIViewController {
#IBOutlet weak var scoreLabel: UILabel!
var score = 0
var lives = 0
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
scoreLabel.text = "Score: \(score)"
}
#IBAction func buyLife(sender: UIButton) {
lives++
}
#IBAction func goRestart(sender: UIButton) {
let destinationVC = self.presentingViewController as! GameViewController
destinationVC.lives = lives
self.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
}
}
Passing only input text from textfield to label in second VC works!! But i want when user type number 10 in uitextfield, ( 1 ticket is 2 euros so 10 tickets * 2 euro is 20) and when i click PAY button, so that SUM can be displayed in label in second VC, i think that viewdidload in VC2 is happening before prepareForSegue, i don't know. It works when i click second time on PAY button, but not when i first click PAY button where label displays zero, help :) Embedded in navigation controller for navigation.
VC1
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var howManyTickets: UITextField!
var sumTicketsAndPriceOfTickets = Int()
var priceOfTicket = 2 // euros
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.
}
#IBAction func platiTeKarte(sender: AnyObject) {
sumTicketsAndPriceOfTickets = howManyTickets.text.toInt()! * priceOfTicket
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let driver = segue.destinationViewController as! primaocViewController
var whatToPass = sumTicketsAndPriceOfTickets
driver.receiver = whatToPass
}
}
VC2
import UIKit
class primaocViewController: UIViewController {
#IBOutlet weak var displaySum: UILabel!
var receiver:Int!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.displaySum.text = String(receiver)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
If your button is wired directly to a segue, then you don't need an #IBAction as well. As you are seeing, the prepareForSegue is happening before the #IBAction for your Pay button. Just compute your value in prepareForSegue:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let driver = segue.destinationViewController as! primaocViewController
driver.receiver = (howManyTickets.text.toInt() ?? 0) * priceOfTicket
}
I changed the calculation of the pay to use the nil coalescing operator ??. This is generally a safer approach because if the toInt() returns nil for any reason, it will in this case just use 0 instead of crashing.