Why is my prepareForSegue code activating the wrong button? - swift

I'm learning how to program and am playing with a Swift project in Xcode. The main storyboard has two view controllers. The first view controller is simply called ViewController and the second view controller is called HelpScreenViewController.
In ViewController I have a "help" button that switches the user to HelpScreenViewController. This button uses a segue called "goToHelpScreenSegue".
In HelpScreenViewController I have three buttons:
"Close" button to dismisses the view (no segue used)
"Send Feedback" button to generate a new email in the Mail app (no segue used)
"Reset Game" button to call a function that is coded within the first ViewController. This third button uses a segue called "resetGameSegue".
What I'm trying to do is...
...Get the "Reset Game" button on the HelpScreenViewController to reset the game by calling a function that's coded within the first view controller.*
To try and get this to work the way I want, I've used the following code:
WITHIN first main ViewController
import UIKit
import iAd
import AdSupport
import AVFoundation //audio
import GameplayKit
class ViewController: UIViewController, ADBannerViewDelegate, MyResetGameProtocol {
#IBOutlet weak var Banner: ADBannerView!
#IBOutlet weak var buttonA: UIButton!
#IBOutlet weak var buttonB: UIButton!
#IBOutlet weak var buttonC: UIButton!
#IBOutlet weak var buttonD: UIButton!
#IBOutlet weak var labelQuestion: UILabel!
#IBOutlet weak var labelScore: UILabel!
#IBOutlet weak var labelTotalQuestionsAsked: UILabel!
#IBOutlet weak var labelFeedback: UILabel!
#IBOutlet weak var buttonNext: UIButton!
var score :Int! = 0
var totalquestionsasked :Int! = 0
var allEntries : NSArray!
var shuffledQuestions: [AnyObject]!
var nextQuestion = -1
var currentCorrectAnswerIndex : Int = 0
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.Banner?.delegate = self
self.Banner?.hidden = true
LoadAllQuestionsAndAnswers()
if #available(iOS 9.0, *) {
shuffledQuestions = GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(allEntries as [AnyObject])
nextQuestion++
LoadQuestion(nextQuestion)
// Fallback on earlier versions
}else{
let randomNumber = Int(arc4random_uniform(UInt32(allEntries.count)))
LoadQuestionPreiOS9(randomNumber)
}
LoadScore()
AdjustInterface()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let viewController = segue.destinationViewController as! HelpScreenViewController
viewController.controller = self
}
func ResetGame() {
PlaySoundReset()
score = 0
totalquestionsasked = 0
SaveScore()
LoadScore()
}
func PlaySoundReset()
{
let alertSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("pcbeep", ofType: "wav")!)
do {
audioPlayer = try AVAudioPlayer(contentsOfURL: alertSound)
} catch {
}
audioPlayer.prepareToPlay()
audioPlayer.play()
}
func SaveScore()
{
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setInteger(score, forKey: "Score")
defaults.setInteger(totalquestionsasked, forKey: "Out of")
}
func LoadScore()
{
let defaults = NSUserDefaults.standardUserDefaults()
score = defaults.integerForKey("Score")
totalquestionsasked = defaults.integerForKey("Out of")
labelScore.text = "Score: \(score)"
labelTotalQuestionsAsked.text = "out of \(totalquestionsasked)"
}
and so on....
WITHIN the second HelpScreenViewController
import UIKit
protocol MyResetGameProtocol {
func ResetGame()
}
class HelpScreenViewController: UIViewController, MyResetGameProtocol {
var controller: MyResetGameProtocol? // reference to the delegate alias First Controller
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
#IBAction func SendFeedback(sender: AnyObject) {
UIApplication.sharedApplication().openURL(NSURL(string: "mailto:feedback#felice.ws?")!)
}
#IBAction func DismissView(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil) }
#IBAction func buttonResetGame(sender: AnyObject) {
controller.ResetGame()
}
}
Now, at the moment with the above code what happens is that if the user taps the "help" button in the first main ViewController (i.e. goToHelpScreenSegue), not only does it take the user to the help screen, but it also calls the function I want activated when the user taps on the "Reset Game" button instead. That is, at the moment, it's the "help" button that resets the game before taking the user to the help screen.
Now, within the help screen, the first two buttons work normally (but they're not using segues). Tapping on the third button (the Reset Game one) simply returns the user back to the main screen. It doesn't call the function, doesn't reset the game.
I've lost count of the times I've changed the code around to try and get it to work right, but I've obviously missed something really obvious.
In particular, I've tried using the following code instead within the main ViewController:
override func prepareForSegue(segue: UIStoryboardSegue?, sender: AnyObject?) {
if segue?.identifier == "resetGameSegue" {
let viewController = segue!.destinationViewController as! HelpScreenViewController
viewController.controller = self
}
However, this results in nothing happening. What I mean is that the button on the main screen works properly (taking the user to the help screen and not incorrectly calling the resetGame function). And, within the help screen the first two buttons work as expected, but the "Reset Game" button just returns the user to the first screen but without calling the ResetGame function.
I also tried removing the IBActions from both my code and the connections inspector for the "Reset Game" button, but that made no difference either.
Any assistance would be most appreciated as I'm just not getting it! :(

I'm agree with MikeG, that you should probably learn about how delegates should be implemented. But the thing you're doing wrong inside this code is that you're not actually calling ResetGame() function on your delegate. Try to implement your #IBAction function in this way:
#IBAction func buttonResetGame(sender: AnyObject) {
controller?.ResetGame()
}
And yeah, if I understand your logic correctly your HelpScreenViewController should not implement MyResetGameProtocol cause your ViewController is the one who's implementing it.

Related

Xcode: didTransition to Won't Run in iMessage Extension

I am making an iMessage extension that uses the didTransition(to:). However, the function won't run when I resize the iMessage extension in the simulator. Am I doing something wrong?
This is the code I have:
import UIKit
import Messages
class EditorViewController: MSMessagesAppViewController {
#IBOutlet weak var input: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
input.text = "not changed"
}
// This part isn't working:
override func didTransition(to presentationStyle: MSMessagesAppPresentationStyle) {
input.text = "changed"
}
}
When I resize it in the simulator, nothing happens. The input.text changes the UITextView's text in the viewDidLoad() function, but not in the didTransition(to) function because it never runs.
Am I doing something wrong?
The EditorViewController is a view controller presented by the show (e.g. Push) segue, and has a NavigationController attached to it.
Here is a gif of it not changing:
The input's text never changes
How can I fix this?
EDIT: The willTransition and didTransition functions don't run when the View Controller is embedded in a Navigation Controller. Is there a way to fix this? (It works without the Navigation Controller, but I need the Navigation Controller for this project).
As pointed out in this answer, the entry point of a iMessage App need to be a subclass of MSMessagesAppViewController, so you can not use a NavigationViewController directly as root controller, until Apple adds support for this behavior.
But as suggested, you could solve this with a workaround like this:
import UIKit
import Messages
class MyRootVC: MSMessagesAppViewController {
var navVC: UINavigationViewController!
var editorVC: EditorViewController!
func viewDidLoad() {
super.viewDidLoad()
editorVC = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController() as! EditorViewController
navVC = UINavigationController(rootViewController: editorVC)
self.addChild(navVC)
self.view.addSubview(navVC.view)
navVC.didMove(toParent: self)
}
override func didTransition(to presentationStyle: MSMessagesAppPresentationStyle) {
editorVC.input.text = "changed"
}
}
class EditorViewController: UIViewController {
#IBOutlet weak var input: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
input.text = "not changed"
}
}

Why would NSWindowController return nil-value window property?

I'm using modal sheets (slide down from top) to get user input. I currently have 2 that I think are identical except for the UI, each a NIB + NSWindowController-subclass pair. One works as expected, binding input to an array controller and table view. When trying to use the other, the window property of the NSWindowController is nil.
This code works:
#IBAction func addItemButtonClicked(_ button: NSButton) {
let window = document?.windowForSheet
let windowController = NewItemSheetController()
windowController.typeChoices = newItemSheetTypeChoices
windowController.windowTitle = newItemSheetTitle
print(#function, windowController.window) // output below
window?.beginSheet(windowController.window!, completionHandler: { response in
// The sheet has finished. Did user click OK?
if response == NSApplication.ModalResponse.OK {
let structure = (self.newItemSheetController?.structure)!
self.document?.dataSource.structures.append(structure)
}
// All done with window controller.
self.newItemSheetController = nil
})
newItemSheetController = windowController
}
The output of the print statement: "addItemButtonClicked(_:) Optional()"
This code doesn't:
#IBAction func addItemButtonClicked(_ button: NSButton) {
let window = document?.windowForSheet
let windowController = NewRecurrenceItemSheetController()
windowController.windowTitle = newItemSheetTitle
print(#function, windowController.window)
window?.beginSheet(windowController.window!, completionHandler: { response in
// The sheet has finished. Did user click OK?
if response == NSApplication.ModalResponse.OK {
let recurrence = (self.newItemSheetController?.recurrence)!
self.document?.dataSource.recurrences.append(recurrence)
}
// All done with window controller.
self.newItemSheetController = nil
})
newItemSheetController = windowController
}
The output of the print statement: "addItemButtonClicked(_:) nil"
Classes NewItemSheetController and NewRecurrenceItemSheetController are subclasses of NSWindowController and differ only with NSNib.Name and properties related to differing UI. As far as I can see, the XIBs and Buttons are "wired" similarly. The XIBs use corresponding File's Owner. Window objects have default class.
#objcMembers
class NewItemSheetController: NSWindowController {
/// other properties here
dynamic var windowTitle: String = "Add New Item"
override var windowNibName: NSNib.Name? {
return NSNib.Name(stringLiteral: "NewItemSheetController")
}
override func windowDidLoad() {
super.windowDidLoad()
titleLabel.stringValue = windowTitle
}
// MARK: - Outlets
#IBOutlet weak var titleLabel: NSTextField!
#IBOutlet weak var typeChooser: NSPopUpButton!
// MARK: - Actions
#IBAction func okayButtonClicked(_ sender: NSButton) {
window?.endEditing(for: nil)
dismiss(with: NSApplication.ModalResponse.OK)
}
#IBAction func cancelButtonClicked(_ sender: NSButton) {
dismiss(with: NSApplication.ModalResponse.cancel)
}
func dismiss(with response: NSApplication.ModalResponse) {
window?.sheetParent?.endSheet(window!, returnCode: response)
}
}
Why does one return instantiate a windowController object with a nil-valued window property?
In Interface Builder, the XIB Window needed to be attached to File's Owner with a Window outlet and delegate. Thanks #Willeke.

AppleTV - tvos - Hybrid app using native and TVMLKIT - Can't back to native app

I'm a beginner on TVOS.
I'd like to create an hybrid app on AppleTV using a native app and TVMLKIT.
My native application is just a simple native app with buttons (using swift).
When we click on a button, I launch a a javascript app using TVLMKIT and TVJS.
My TVJS as uses the Player to display a video.
When the video is over, I want to close the TVJS app and back to the native ViewController.
My problem is that when I back to native app, I loose the focus on my native View (the app is frozen).
native ViewController:
import UIKit
import TVMLKit
class ViewController: UIViewController, TVApplicationControllerDelegate {
var window: UIWindow?
var appController: TVApplicationController?
var appControllerContext = TVApplicationControllerContext();
static let TVBaseURL = "http://localhost:9001/"
static let TVBootURL = "\(ViewController.TVBaseURL)/client/js/application.js"
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.
}
#IBOutlet weak var label: UILabel!
#IBOutlet weak var viewAd: UIView!
#IBAction func clickOnlaunchAd(sender: AnyObject) {
window = UIWindow(frame: UIScreen.mainScreen().bounds)
guard let javaScriptURL = NSURL(string: ViewController.TVBootURL) else {
fatalError("unable to create NSURL")
}
appControllerContext.javaScriptApplicationURL = javaScriptURL
appControllerContext.launchOptions["BASEURL"] = ViewController.TVBaseURL
appController = TVApplicationController(context: appControllerContext, window: window,delegate: self)
}
#IBAction func clickOnChangeText(sender: AnyObject) {
label.text = "changed";
}
func appController(appController: TVApplicationController, didStopWithOptions options: [String : AnyObject]?) {
self.setNeedsFocusUpdate()
self.updateFocusIfNeeded()
}
func appController(appController: TVApplicationController, evaluateAppJavaScriptInContext jsContext: JSContext){
let notifyEventToNative : #convention(block) (NSString!) -> Void = {
(string : NSString!) -> Void in
print("[log]: \(string)\n")
self.appController?.stop()
}
jsContext.setObject(unsafeBitCast(notifyEventToNative, AnyObject.self), forKeyedSubscript: "notifyEventToNative")
}
}
Just before calling "notifyEventToNative" from my TVJS, I call "navigationDocument.clear();" to clear the TVML view.
I can see my native app but I can't interact with it.
Any ideas?
Thanks.
I also had the same problem. I was opened a TVML document from the UIViewController. And I also lost the focus. So, first of all I can advice you to override var called preferredFocusedView in your ViewController. In this method you can return reference to viewAd. But the better solution would be to wrap your ViewController into the TVML-item (with the TVMLKit framework). In that case I hope that you will have no problems with focus because you will use TVML during the whole application.

what to recast as an NSSplitViewItem

I am trying to self-learn OSX application development so I can make up all of my own bad habits 8).
Probably extraneous information
I have a trial app that works successfully - it resizes itself based on input from the user via a slider.
The key piece of code that does this is in one View controller ...
class JunkViewController2: NSViewController {
var myY: CGFloat!
#IBOutlet weak var mySlider: NSSlider!
#IBOutlet weak var myView: NSView!
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
self.preferredContentSize = NSMakeSize(self.view.frame.width, 83)
}
#IBAction func mySlider(sender: NSSlider) {
let mySplitViewController = self.childViewControllers[0] as! JunkSplitViewController
switch mySlider.intValue {
case 3:
myY = 140.0
mySplitViewController.splitViewItems[2].collapsed = false
mySplitViewController.splitViewItems[1].collapsed = false
mySplitViewController.showSubview(2)
mySplitViewController.showSubview(1)
mySplitViewController.showSubview(0)
case 2:
myY = 110.0
mySplitViewController.splitViewItems[2].collapsed = true
mySplitViewController.splitViewItems[1].collapsed = false
mySplitViewController.hideSubview(2)
mySplitViewController.showSubview(1)
mySplitViewController.showSubview(0)
default:
myY = 80.0
mySplitViewController.splitViewItems[2].collapsed = true
mySplitViewController.splitViewItems[1].collapsed = true
mySplitViewController.hideSubview(2)
mySplitViewController.hideSubview(1)
mySplitViewController.showSubview(0)
}
mySplitViewController.preferredContentSize = NSMakeSize(self.view.frame.width, myY - 50 + 3)
self.preferredContentSize = NSMakeSize(self.view.frame.width, myY + 3)
}
}
More pertinent information
In what is working, above, on the story board I have three duplicate ViewControllers connected to a SplitView controller. I do a bunch of what feels like belts and suspenders work to make sure that everything gets resized properly - but the key part (I think) is the .collapsed property.
I am now trying to accomplish the same thing, using a completely different method - dynamically adding / removing split view items. This should allow me to have only one of the small ViewControllers on my story board, and then instantiate it as needed.
Following that idea, here is my SplitViewController ...
class JunkSplitViewController: NSSplitViewController {
#IBOutlet weak var mySplitView: NSSplitView!
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
//mySplitView.adjustSubviews()
}
func makeChild() -> SmallViewController {
let mySmallGroup = NSStoryboard(name: "Main", bundle: nil).instantiateControllerWithIdentifier("smallVwCtl")
self.addSplitViewItem(mySmallGroup as! NSSplitViewItem)
return mySmallGroup as! SmallViewController
}
}
The main view controller invokes the makeChild function.
class JunkViewController: NSViewController {
#IBOutlet weak var mySlider: NSSlider!
#IBOutlet weak var myView: NSView!
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
self.preferredContentSize = NSMakeSize(self.view.frame.width, 83)
}
#IBAction func mySlider(sender: NSSlider) {
let mySplitViewController = self.childViewControllers[0] as! JunkSplitViewController
while mySlider.intValue.toIntMax() > mySplitViewController.splitViewItems.count.toIntMax() {
mySplitViewController.makeChild()
}
while mySlider.intValue.toIntMax() < mySplitViewController.splitViewItems.count.toIntMax(){
mySplitViewController.splitViewItems.removeLast()
}
}
}
I get an error at the self.addSplitViewItem(mySmallGroup as! NSSplitViewItem) line of JunkSplitViewController ... "Could not cast value of type Scratch2.SmallViewController to NSSplitViewItem"
I've tried a handful of combinations (forcing mySmallGroup, 'self.addSplitViewItem(mySmallGroup as! SmallViewController)`, etc.) Everything leads to a similar error, either at compile or run time.
I cannot find any documentation on SplitViewItem.
So the question - what will work as input to addSplitViewItem and still successfully connect a new instance of SmallViewController?
And gratefully accept any comments/feedback on the methodology
I hate it when I find my answer minutes after posting a question ...
Based on info I found here ...
func makeChild() -> SmallViewController {
let mySmallGroup = NSStoryboard(name: "Main", bundle: nil).instantiateControllerWithIdentifier("smallVwCtl") as! SmallViewController
self.addSplitViewItem(NSSplitViewItem(viewController: mySmallGroup))
return mySmallGroup
}
... but I'd still like to hear any feedback on methodology. Thanks.

UISlider, save status

My question is about the UISlider. I managed to implement everything but i don't know how i can save its status.
Ive looked everywhere but al the posts are in older versions of swift/xcode. So the question is how do i save its status so that when i go to another view and then come back the status is still the same.
Thanks very much!
import UIKit
class SettingsViewController: UIViewController {
var sequeInt = 0
let savedWordLength = NSUserDefaults.standardUserDefaults()
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBOutlet weak var wordLength: UISlider!
#IBOutlet weak var wordLengthValue: UILabel!
var selectedValue: Int = 5
#IBAction func valueChanged(sender: UISlider) {
selectedValue = Int(sender.value)
savedWordLength.setInteger(selectedValue, forKey: "myInt")
let ourInt = savedWordLength.integerForKey("myInt")
sequeInt = ourInt
print (sequeInt)
wordLengthValue.text = String(ourInt)
}
UISlider value property is a Float so you can use NSUserDefault's method setFloat to save its value and retrieve it next time your view appears using NSUserDefaults method floatForKey.
to save it:
NSUserDefaults.standardUserDefaults().setFloat(sender.value, forKey: "wordLength")
load it:
override func viewWillAppear(animated: Bool) {
wordLength.setValue(NSUserDefaults.standardUserDefaults().floatForKey("wordLength"), animated: false)
}
Follow these steps.
In your view will appear.
fontSlider.setValue(UserDefaults.standard.float(forKey: "slider_value"), animated: false)
Take another outlet from storyBoard as "editingDidEnd".
In that function:
UserDefaults.standard.set(fontSlider.value, forKey: "slider_value")
And finally in your ValueChanged Outlet.
UserDefaults.standard.set(fontSlider.value, forKey: "slider_value")