I'm building a Quiz App and at the end of the quiz, upon clicking an answer, I want the screen to transition to the ResultViewController where the user's score is displayed. I linked a segue from my MCViewController to the new ResultViewController screen. But upon finishing the quiz, the screen just kinda goes dim, no errors.
MCViewController:
import UIKit
class MCViewController: UIViewController {
#IBOutlet weak var questionLabel: UILabel!
#IBOutlet weak var progressBar: UIProgressView!
#IBOutlet weak var aButton: UIButton!
#IBOutlet weak var bButton: UIButton!
#IBOutlet weak var cButton: UIButton!
#IBOutlet weak var dButton: UIButton!
let quiz =
[
Questions(q: "When did English settlement begin in Canada?", a: "1510", b: "1497", c: "1604", d: "1720", answer: "1604"),
Questions(q: "Who passed the Quebec Act of 1774?", a: "Canadian Parliament", b: "British Parliament", c: "Quebec Parliament", d: "The French majority", answer: "British Parliament"),
Questions(q: "Whose portrait is on the Canadian 10 dollar bill?", a: "Sir George Cartier", b: "Sir Etienne Tache", c: "Sir John A. Macdonald", d: "Sir Louis La Fontaine", answer: "Sir John A. Macdonald"),
Questions(q: "What are the responsibilities of the federal government?", a: "Matters of national and international concern.", b: "Matters of national concern.", c: "Matters of international concern.", d: "Matters of provincial concern.", answer: " Matters of national and international concern."),
Questions(q: "What is 'Habeas corpus'?", a: "The right to challenge unlawful detention by the state.", b: "The right to live and work anywhere in Canada.", c: "The right to speak freely.", d: " The right for peaceful assembly.", answer: "The right to challenge unlawful detention by the state.")
]
var questionNumber = 0
override func viewDidLoad() {
super.viewDidLoad()
updateUI()
}
#IBAction func answerPressed(_ sender: UIButton) {
let userAnswer = sender.currentTitle
let actualAnswer = quiz[questionNumber].answer
if (userAnswer == actualAnswer) {
sender.backgroundColor = UIColor.green
} else {
sender.backgroundColor = UIColor.red //END OF ARRAY, SHOULD TRANSITION TO RESULTVIEWCONTROLLER
}
if (questionNumber + 1 < quiz.count){
questionNumber += 1
} else {
let resultVC = ResultViewController()
self.present(resultVC, animated: true, completion: nil)
}
Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(updateUI), userInfo: nil, repeats: false)
}
#objc func updateUI() {
questionLabel.text = quiz[questionNumber].q
aButton.setTitle(quiz[questionNumber].a, for: .normal)
bButton.setTitle(quiz[questionNumber].b, for: .normal)
cButton.setTitle(quiz[questionNumber].c, for: .normal)
dButton.setTitle(quiz[questionNumber].d, for: .normal)
aButton.titleLabel?.adjustsFontSizeToFitWidth = true;
bButton.titleLabel?.adjustsFontSizeToFitWidth = true;
cButton.titleLabel?.adjustsFontSizeToFitWidth = true;
dButton.titleLabel?.adjustsFontSizeToFitWidth = true;
aButton.backgroundColor = UIColor.clear
bButton.backgroundColor = UIColor.clear
cButton.backgroundColor = UIColor.clear
dButton.backgroundColor = UIColor.clear
progressBar.progress = Float(questionNumber + 1) / Float(quiz.count)
}
}
ResultViewController:
import UIKit
class ResultViewController : UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
}
}
Here's a little bit of my picture if it helps:
This code...
let resultVC = ResultViewController()
self.present(resultVC, animated: true, completion: nil)
presents a ResultViewController that is created using the init initialiser, not from the storyboard. It is likely the case that you haven't written anything in ResultViewController.init, because you did everything in the storyboard, so it does nothing and shows a blank view.
Since you have a segue, you should perform that segue instead!
performSegue(withIdentifier: "MCResult", sender: nil)
You probably have some data that you want to pass to ResultViewController. Do that in prepare(for:):
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? ResultViewController {
// pass whatever you need to "vc"
}
}
You are in same storyBoard so, instantiate your viewController like
let resultVC = self.storyboard?.instantiateViewController(withIdentifier: "ResultViewControllerID") as! ResultViewController
self.present(resultVC, animated: true, completion: nil)
I am hoping, you are aware of the UIViewController storyboard ID
Related
I am making an app that will display a random quote from a stoic philosopher. Right now, I am stuck on trying to make the correct picture pop up. (User clicks on a Button with the philosopher's name on it, and then a new view pops up with an image of the philosopher and a random quote by him).
class ViewController: UIViewController {
var allQuotes = [String]()
var pictures = [String]()
#IBOutlet var Epictetus: UIButton!
#IBOutlet var Seneca: UIButton!
#IBOutlet var MarcusAurelius: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Create a constant fm and assign it the value returned by FileManager.default (built in system type)
let fm = FileManager.default
// Declares a new constant called path that sets the resource path of ours apps buddle.
// A bundle is a directory containing our compiled program and all our assets
let path = Bundle.main.resourcePath!
// items array will be a constant collection of the names of all the files found in the directory of our app
let items = try! fm.contentsOfDirectory(atPath: path)
// create a loop to go through all of our items...
for item in items {
if item.hasSuffix("jpg"){
pictures.append(item)
}
}
print(pictures)
title = "Stoicism"
if let stoicQuotesURL = Bundle.main.url(forResource: "quotes", withExtension: "txt"){
if let stoicQuotes = try? String(contentsOf: stoicQuotesURL) {
allQuotes = stoicQuotes.components(separatedBy: "\n\n")
}
}
}
#IBAction func buttonTapped(_ sender: UIButton) {
if sender.tag == 0 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[0]
navigationController?.pushViewController(vc, animated: true)
}
}
else if sender.tag == 1 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[1]
navigationController?.pushViewController(vc, animated: true)
}
}
else if sender.tag == 2 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[2]
navigationController?.pushViewController(vc, animated: true)
}
}
}
}
That's the code for my main viewController.
import UIKit
class PictureViewController: UIViewController {
#IBOutlet var picture: UIImageView!
#IBOutlet var imageView: UIImageView!
var selectedImage: String?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if let imageToLoad = selectedImage {
imageView.image = UIImage(named: imageToLoad)
}
}
override func viewWillAppear(_ animated: Bool) {
// doing it for the parent class
super.viewWillAppear(animated)
// if its a nav Cont then it will hide bars on tap...
}
// now make sure it turns off when you go back to the main screen
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
That's the code for the viewController that has the imageView. Right now, the image that's popping up is always the preset (Marcus Aurelius), even though my code looks correct to me. Obviously it isn't (also, I've already debugged and ensured through print statements that the jpg files add to the pictures array correctly).
Any help would be appreciated.
First of all, this code is really silly:
#IBAction func buttonTapped(_ sender: UIButton) {
if sender.tag == 0 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[0]
navigationController?.pushViewController(vc, animated: true)
}
}
else if sender.tag == 1 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[1]
navigationController?.pushViewController(vc, animated: true)
}
}
else if sender.tag == 2 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[2]
navigationController?.pushViewController(vc, animated: true)
}
}
}
Do you see that everything in those lines is identical except for the numbers? So make the number a variable:
#IBAction func buttonTapped(_ sender: UIButton) {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
print(sender.tag)
vc.selectedImage = pictures[sender.tag]
navigationController?.pushViewController(vc, animated: true)
}
}
See how much shorter and clearer that is? Okay, I've also added a print statement. This will print the tag to the console. You need to make sure that your buttons do have the right tags. If they do, your code should work.
I am making a game in Swift that records the high score and initials (similar to pinball records) and displays it on the game screen as labels. I am using the code below to record the high score and initials on my phone and update them. For the time being, this program will only be on my phone so I am not concerned about storing or updating data on a remote database.
What I don't know how to do (yet) is make a little popup asking the user to enter their initials on a keyboard when the quit button is pressed, if they have the current high score. Ideally, I want it to be able to only accept 3 characters and to update the initials label immediately, before the user is taken to a different view controller.
#IBOutlet weak var scoreLabel: UILabel!
#IBOutlet weak var highScoreLabel: UILabel!
#IBOutlet weak var highScoreInitialsLabel: UILabel!
var score : Int = 0
//Stores and sets high score initials
var oldHighScoreInitials : String = "AAA"
var highScoreInitials : String {
get {
return UserDefaults.standard.string(forKey: "highScoreInitials") ?? "AAA"
}
set {
UserDefaults.standard.set(newValue, forKey: "highScoreInitials")
}
}
//Stores and sets high score
var oldHighScore : Int = 0
var highScore : Int {
get {
return UserDefaults.standard.integer(forKey: "highScore")
}
set {
UserDefaults.standard.set(newValue, forKey: "highScore")
}
}
override func viewDidLoad() {
super.viewDidLoad()
//Updates high score and initials labels with stored highest score and associated initials
highScoreLabel.text = String(highScore)
highScoreInitialsLabel.text = String(highScoreInitials)
oldHighScore = highScore
oldHighScoreInitials = highScoreInitials
}
//Asks for initials if new high score, and segues to Main VC
#IBAction func quitButtonPressed(_ sender: AnyObject) {
if (score > highScore){
highScore = score
print("Ask for initials")
print("Game over, thanks for playing!"
print("Segue to Main VC")
}
else {
print("Game over, thanks for playing!")
print("Segue to Main VC")
}
}
Thank you very much to anybody that offers assistance or advice.
Here you go, pretty simple using UIAlertViewController:
//Asks for initials if new high score, and segues to Main VC
#IBAction func quitButtonPressed(_ sender: AnyObject) {
if (score > highScore){
highScore = score
let alert = UIAlertController(title: "NEW HIGH SCORE", message: "Please enter your initials", preferredStyle: .alert)
alert.addTextField(configurationHandler: configurationTextField)
alert.addAction(UIAlertAction(title: "DONE", style: .default, handler:{ (action) in
//First example of updating initials
guard alert.textFields![0].text?.characters.count != 0 else{
return
}
self.oldHighScore = self.highScore
self.oldHighScoreInitials = alert.textFields![0].text
//Segue to Main VC
}))
present(alert, animated: true, completion: nil)
}
else {
print("Game over, thanks for playing!")
print("Segue to Main VC")
}
}
func configurationTextField(textField: UITextField!){
textField.delegate = self
textField.textAlignment = .center
textField.placeholder = "_ _ _"
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let text = textField.text else { return true }
let count = text.count + string.count - range.length
return count <= 3
}
//Second example of updating initials
func textFieldDidEndEditing(_ textField: UITextField) {
oldHighScore = highScore
oldHighScoreInitials = textField.text
}
And finally your controller should conform to the UITextFieldDelegate protocol:
class yourController: UIViewController, UITextFieldDelegate
After working with Arie Pinto, we determined that my segue was preventing the alert from working and was presenting an error stating that my program was attempting to present an alert whose view was not in the window hierarchy.
I programmed my segue by ctrl+dragging my quit button to a different view controller, without writing any code.
My solution to this will be to write in performSegue code in the quitButtonPressed function to allow the alert and initial update code to perform their actions before changing windows.
I was trying to animate a set of images using this tutorial: https://www.geekylemon.com/animated-loading-screen. I converted the code from objective c to swift in order to implement it into another project, but it seems like it doesn't work properly.
Well, on the the storyboard are three Image Views(AnitmationimageView beeing the same with "L1.png", Loadimageview and the background image of the view controller). I wanted to run the AnitmationimageView and after it a transition to L1,2,3,4 images and after them to Loadimageview and after it to the background image. But when I run the program it only shows me the transition from AnitmationimageView to Loadimageview and the transition from Loadimageview to the background image. Hope this helps in "diagnosing" my problem.
class ViewController: UIViewController {
#IBOutlet weak var AnitmationimageView: UIImageView!
#IBOutlet weak var Loadimageview: UIImageView!
var loading_1: UIImage!
var loading_2: UIImage!
var loading_3: UIImage!
override func viewDidLoad() {
super.viewDidLoad()
AnitmationimageView.animationImages = [UIImage(named: "L1.png"), UIImage(named: "L2.png"), UIImage(named: "L3.png"), UIImage(named: "L4.png")] as? [UIImage]
AnitmationimageView.animationRepeatCount = 1
AnitmationimageView.animationDuration = 3 as CFTimeInterval
AnitmationimageView.startAnimating()
perform(#selector(self.delay1), with: nil, afterDelay: 3)
delay1()
delay2()
delay3()
}
#objc func delay1() {
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDuration(1.0)
AnitmationimageView.alpha = 0
UIView.commitAnimations()
perform(#selector(self.delay2), with: nil, afterDelay: 1.0)
}
#objc func delay2() {
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDuration(1.0)
Loadimageview.alpha = 1
UIView.commitAnimations()
perform(#selector(self.delay3), with: nil, afterDelay: 1.5)
}
#objc func delay3() {
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDuration(1.0)
Loadimageview.alpha = 0
UIView.commitAnimations()
}
}
In my app I have 2 UIButtons, each button plays 2 sounds (plays one sound, pauses, plays the other sound), and the UIButtons image changes every time it is pressed, my app also has a few tabs.the problem is that when I press a UIButton (which takes about 4 seconds for the sounds to be played and be done) I can not press another button, or even switch between tabs, the app freezes until the action of UIbutton gets done with, then I can press other buttons or switch tabs.
here's my code:
import UIKit
import AVFoundation
import CoreData
class FirstViewController: UIViewController {
var player:AVAudioPlayer = AVAudioPlayer()
var player2:AVAudioPlayer = AVAudioPlayer()
#IBAction func play(_ sender: Any) {
player.play()
sleep(1)
player2.play()
}
#IBAction func play2(_ sender: Any) {
player.play()
sleep(1)
player2.play()
}
#IBOutlet weak var play: UIButton!
#IBOutlet weak var play2: UIButton!
var buttonActive = false
#IBAction func pPressed(_ sender: Any) {
if buttonActive {
play.setBackgroundImage(UIImage(named: "SwitchD-Icon-40"), for: .normal)
} else {
play.setBackgroundImage(UIImage(named: "Switch-Icon-40"), for: .normal)
}
buttonActive = !buttonActive
}
#IBAction func p2Pressed(_ sender: Any) {
if buttonActive {
play2.setBackgroundImage(UIImage(named: "SwitchD-Icon-40"), for: .normal)
} else {
play2.setBackgroundImage(UIImage(named: "Switch-Icon-40"), for: .normal)
}
buttonActive = !buttonActive
}
override func viewDidLoad() {
super.viewDidLoad()
play.setBackgroundImage(UIImage(named: "SwitchD-Icon-40"), for: .normal)
play2.setBackgroundImage(UIImage(named: "SwitchD-Icon-40"), for: .normal)
do
{
let audioPath = Bundle.main.path(forResource: "DTMF-1", ofType: "mp3")
try player = AVAudioPlayer(contentsOf: NSURL(fileURLWithPath: audioPath!) as URL)
let audioPath2 = Bundle.main.path(forResource: "DTMF-2", ofType: "mp3")
try player2 = AVAudioPlayer(contentsOf: NSURL(fileURLWithPath: audioPath2!) as URL)
}
Is there any way to make my app be faster?
One famous way to implement do something, pause and do other thing is to use Timer.
The following code also includes:
How to properly instantiate AVAudioPlayer
How to invalidate working Timer
How to stop AVAudioPlayer when it may be reused soon
How to stop playing on transition
Please try:
class FirstViewController: UIViewController {
var player: AVAudioPlayer? //### You should not instantiate unused objects
var player2: AVAudioPlayer?
var secondSoundTimer: Timer?
private func startPlaying() {
stopPlaying()
player?.play()
//### Using `Timer` is one way to achieve pause-like thing.
//### If you need to target older iOSs than 10.0, you need to modify this part.
secondSoundTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) {timer in
self.player2?.play()
self.secondSoundTimer?.invalidate()
self.secondSoundTimer = nil
}
}
private func stopPlaying() {
secondSoundTimer?.invalidate()
secondSoundTimer = nil
if let player = player, player.isPlaying {
//### Stop playing (keeping internal `prepared` state) and rewind to the first place.
player.pause()
player.currentTime = 0
}
if let player2 = player2, player2.isPlaying {
player2.pause()
player2.currentTime = 0
}
}
#IBOutlet weak var play: UIButton!
#IBOutlet weak var play2: UIButton!
var buttonActive = false
#IBAction func pPressed(_ sender: Any) {
startPlaying()
if buttonActive {
play.setBackgroundImage(UIImage(named: "SwitchD-Icon-40"), for: .normal)
} else {
play.setBackgroundImage(UIImage(named: "Switch-Icon-40"), for: .normal)
}
buttonActive = !buttonActive
}
//### Don't you need `button2Active`?
#IBAction func p2Pressed(_ sender: Any) {
startPlaying()
if buttonActive {
play2.setBackgroundImage(UIImage(named: "SwitchD-Icon-40"), for: .normal)
} else {
play2.setBackgroundImage(UIImage(named: "Switch-Icon-40"), for: .normal)
}
buttonActive = !buttonActive
}
override func viewDidLoad() {
super.viewDidLoad()
play.setBackgroundImage(UIImage(named: "SwitchD-Icon-40"), for: .normal)
play2.setBackgroundImage(UIImage(named: "SwitchD-Icon-40"), for: .normal)
do {
//### You can get URL directly from Bundle
//### It's better to apply forced-unwrapping as soon as possible, if you need it eventually.
let audioURL = Bundle.main.url(forResource: "DTMF-1", withExtension: "mp3")!
player = try AVAudioPlayer(contentsOf: audioURL)
//### `prepareToPlay()` improves response for the first play.
player?.prepareToPlay()
let audioURL2 = Bundle.main.url(forResource: "DTMF-2", withExtension: "mp3")!
player2 = try AVAudioPlayer(contentsOf: audioURL2)
player2?.prepareToPlay()
} catch {
print(error)
//... or any other things you need...
}
//...
}
//### Stop playing on transition to other ViewConrollers.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
stopPlaying()
}
}
I am looking for a way to call a custom alert view from multiple view controllers. So far I have made several different attempts without success.
I created an alert view with an interface builder that works fine on one view controller but not the other.
I then tried creating the alert view programmatically thinking it may have something to do with the outlets not being connected on the other view controller. This one also worked on one view controller and not the other.
I made a separate swift file and made a public function and the same result. With this last method, I am able to successfully re-use a regular UIAlertController on multiple view controllers but that is not exactly what I am looking for.
With the first two methods, I do not get any compiling errors. The app runs fine and then crashes when I call the alert from another view controller.
Thanks in advance for any input!
EDIT:
This example works when I put it in another swift file.
public func showSimpleAlert(title: String, message: String?, presentingController: UIViewController) {
if IS_OS_8_OR_LATER() {
let controller = UIAlertController(title: title, message: message, preferredStyle: .Alert)
controller.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: { (action) -> Void in
}))
presentingController.presentViewController(controller, animated: true, completion: nil)
} else {
let alert = UIAlertView(title: title, message: message, delegate: nil, cancelButtonTitle: "OK")
alert.show()
}
}
This is the one I want to work on.
public func showAlert(oMsg: String, oTitle:String) {
alertView.backgroundColor = UIColor.whiteColor()
alertView.layer.cornerRadius = 25
alertTitleLabel.text = oTitle as String
alertTitleLabel.font = UIFont(name: "Open-Sans-Bold", size: 20)
alertTitleLabel.textColor = UIColor.blackColor()
alertTitleLabel.textAlignment = .Center
alertTitleLabel.numberOfLines = 1
alertTitleLabel.frame = CGRectMake(25, 60, 264, 112)
alertLabel.text = oMsg as String
alertLabel.font = UIFont(name: "Open-Sans", size: 20)
alertLabel.textColor = UIColor.blackColor()
alertLabel.textAlignment = .Center
alertLabel.numberOfLines = 4
alertLabel.frame = CGRectMake(25, 130, 264, 112)
okButton.setTitle("OK", forState: .Normal)
okButton.setTitleColor(UIColor.blueColor(), forState: .Normal)
okButton.frame = CGRectMake(60, 230, 197, 75)
okButton.addTarget(UIViewController.self, action:#selector(LoginViewController.buttonAction(_:)), forControlEvents: .TouchUpInside)
}
I will give the answer for a simple custom alertview which is basically a modified uiviewcontroller. you can use a uiviewcontroller as a uialertviewcontroller as follow.
Simple AlertView::
The AlertVC:
import UIKit
class ErrorAlert: UIViewController {
var titlenote:String = ""
var message:String = ""
#IBOutlet weak var cancelBtn: UIButton!
#IBOutlet weak var messageHolder: UILabel!
#IBOutlet weak var imageHolder: UIImageView!
#IBOutlet weak var titleHolder: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.7)
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.messageHolder.text = self.message
self.titleHolder.text = self.titlenote
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func dismiss(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
}
This viewcontroller can be reuse in any vc and any number of times.
Useage Example::
let alertController = self.storyboard?.instantiateViewController(withIdentifier: "erroralert") as! ErrorAlert
alertController.titlenote = "Invalid login"
alertController.message = "Invalid facebook account."
alertController.providesPresentationContextTransitionStyle = true
alertController.definesPresentationContext = true
alertController.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
alertController.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
self.present(alertController, animated: true, completion: nil)
I have made the background of the alertviewvc semitransparent by setting the alpha value.
Actual Display ::
You can make more complex alertview by this method but for reusability you have apply some logic as the button actions will be different for different viewcontroller. Example -- Sometime you can use the alertview for logout alert or sometime for submitting a form .So in both cases the action will be different so for reusability you have to write extra logic.
Another alertView::
I hope my answer will help you.:)