I am currently working through the "Intro to iOS App Development with Swift" course through Udacity. I have copied the code exactly up to the point where we finish recording the audio. This is what they tell you to type:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if (segue.identifier == "stopRecording"){
let playVC:PlayViewController = segue.destinationViewController as PlayViewController
let data = sender as RecordedAudio
playVC.receivedAudio = data } }
However, it returns a compiler error and asks me to add the exclamation point after both as. When I run the program, it says "I found nil while unwrapping an optional". I'm relatively new to programming, so any advice would be helpful.
I just completed this course. So basicly what you are tring to do is passing data between differnet screens. Try to understand what are you tring to achive and it help you to understand the code better.
The main task for the first screen is to record audio, after the task completed, you store all the information about the completed task into an object called RecordedAudio. The information included are var title: String! and var filePathURL: NSURL!. After we store the recording info., we are ready to pass it to the next screen's controller, namely PlayScreenController. First, we have to get access to the conroller and than passing the data.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
// Checking segue if there are more than one
if (segue.identifier == "stopRecording") {
// Get the PlayScreenController access
let playSoundVC = segue.destinationViewController
as playScreenViewController
// Cast sender into a RecordedAudio Object
let data = sender as RecordedAudio
// Passing the data to PlayScreen
playSoundVC.audioData = data
}
}
Now everything is prepared, we can perform the segue. During perform the segue we have to provide the identity of segue and who are the sender.
var recordedAduio = RecordedAduio()
recordedAduio.filePathURL = recorder.url
recordedAudio.title = recorder.url.lastPathComponent
self.performSegueWithIdentifier("stopRecording", sender: recordedAudio)
Pay attention to the sender object in performSegueWithIdentifier that the reason why we can cast the send in prepareSegue into a RecordedAudio object.
NOTE: Remeber to define var audioData: RecordedAudio! in the PlayScreenViewController otherwise you cannot pass the data to the second screen, becuase there is no variable can hold the data which you are trying to pass.
Related
I am trying to read a byte array from my one view controller to another, please find my code below.
From my First View
class First: UIViewController {
var myByteArray = [UInt8](repeating:0, count: 20)
viewDidLoad(){
......}
Few statements later hers where I read my data in a function
func passThis(){
let ReceiveData = rxCharacteristic?.value
if let ReceiveData = ReceiveData {
let ReceivedNoOfBytes = ReceiveData.count
myByteArray = [UInt8](repeating: 0, count: ReceivedNoOfBytes)
(ReceiveData as NSData).getBytes(&myByteArray, length: ReceivedNoOfBytes)
print("Data Received ",myByteArray)
}
This is my Second View that I'm trying to read my array from First View
class Second: UIViewController {
var myByteArray2 = [UInt8](repeating: 0, count: 20)
viewDidLoad(){
super.viewDidLoad()
let fvc = First()
myByteArray2 = fvc.myByteArray
print(myByteArray2)
}
Now I have [11,12,13,14,15,16,17,18,19,20] from myByteArray
but have [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] from myByteArray2 ?
Can somebody help?
Also how do I clear the readValue buffer in rxCharacterstic before writing and reading new values?
Any help/comments appreciated.
Thanks
EDIT -> How my passing is done
From BLECentral
class BLECentral: ...
var centralManager: CBCentralManager!
//After Scanning and connecting
func centralManager(_central: CBCentralManager, didConnect peripheral: CBPeripheral){
peripheral.delegate = self
peripheral.discoverServices([BLEUUID])
//Once connected, move to new view controller to manager incoming and outgoing data
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let firstVC = storyboard.instantiateViewController(withIdentifier: "First") as! First
firstVC.peripheral = peripheral
navigationController?.pushViewController(firstVC, animated: true)
}
Now in my First under prepare for segue block I'm passing the peripheral like this
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.destination is Second
{
let vc2 = segue.destination as? Second
vc2.periperal = blePeripheral
}
}
You are creating a new First view controller from your Second view controller, instead of accessing the one already created.
//Create a new View Controller
let fvc = First()
So you have two First that are made now. What you want to do is access the already created First view controller. Assuming there is no other way, you want to have a "Singleton". This is a very bad way to handle this, as I'll explain later, and there is most likely a better way, but I'm going to give a solution to access First from Second if they never communicate, but First is already created.
If you declare in First a piece of code like:
static let shared = First()
Then that singleton can be accessed via
let first = First.shared
That being said, that's a really bad way of handling communication between view controllers. If you call Second from First, you should pass the data from First to Second (or you could pass a reference of First to Second so Second can access First).
There is generally good ways to pass data between view controllers in the
func prepare(for segue: UIStoryboardSegue, sender: Any?)
method before you navigate. Whenever, whatever, makes the Second view controller should pass it the data it needs.
Lastly, another reason the Singleton view controller is a terrible idea is that it gets away from the MVC concept. So if you can't pass the proper data to Second, you probably need a new class that handles the data for you that both First and Second can work with which is the Model in MVC.
Using a button to collect a phone number and then reading and storing the phone number under the button function.
#IBAction func viewRewardsButtonTapped(_ sender: UIButton) {
view.endEditing(true)
// Validate format
let phoneNumberInput = phoneInput.text
}
Is there a better way to store the phone number phoneNumberInput and get it to another UIViewController?
I currently can't get the other UIViewController to recognize the variables stored under the #IBAction function.
You could use UserDefaults:
// Set phone number
UserDefaults.standard.set(phoneInput,text, forKey: "phoneNumber")
// Get phone number
UserDefaults.standard.string(forKey: "phoneNumber")
Or if you want to segue at the same time that you´re clicking the button use this:
self.performSegue(withIdentifier: "showVC2", sender: phoneNumber.text)
override func prepare(for segue: UIStoryboardSegue, sender: Any!) {
if (segue.identifier == "showVC2") {
if let phoneNumber = sender as? String {
let vc = segue.destination as! YourSecondVC
vc.phoneNumber = phoneNumber
}
}
}
So you basically create the segue and declare a variable for phoneNumber in your second VC so that you can pass the value to it.
Your phoneNumberInput variable is only stored in the scope of your action handler method viewRewardsButtonTapped, so once this method is left the value is lost. You should add a member variable (var phoneNumber: String?) to your class and use this for storing the value. Then, when you open the next view controller, you can pass the value in as a parameter.
If you are using storyboard segues (which I assume), you have to implement performSegue withIdentifier, cast the destination view controller to the concrete type and set the value (e.g. targetVC.phoneNumber = self.phoneNumber).
I am using Swift and Firebase and I am trying to pass the UID in a prepare for segue. So when the user logs in and does something on the next view it will know it is him/her. However, when I do this I keep getting nil
This is the code that I have tried.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "fromNewUserToAddCard" {
if let destination = segue.destination as? AddCardViewController {
destination.uid = tempUID
}
}
}
I have a property on the AddCardViewController of uid that tempUID is supposed to transfer to.
How can I fix this?
Thanks
Why don't you simply ask for FIRAuth.auth()?.currentUser?.uid each time you want the user's uid? This is easier than handling the uid storage yourself
Context: iOS App written in Swift 3 powered by Firebase 3.0
Challenge: On my app, the user's currentScore is stored on Firebase. This user can complete/un-complete tasks (that will increase/decrease its currentScore) from several ViewControllers.
Overview of the App's architecture:
ProfileVC - where I fetch the currentUser's data from Firebase & display the currentScore.
⎿ ContainerView
⎿ CollectionViewController - users can update their score from here
⎿ DetailsVC - (when users tap on a collectionView cell) - again users can update their score from here.
Question: I need to pass the currentScore to the VCs where the score can be updated. I thought about using prepare(forSegue) in cascade but this doesn't work since it passes "nil" before the query on ProfileVC is finished.
I want to avoid having a global variable as I've been told it's bad practice.
Any thoughts would be much appreciated.
Why don't you create a function that will pull all data before you do anything else.
So in ViewDidLoad call...
pullFirebaseDataASYNC()
Function will look like below...
typealias CompletionHandler = (_ success: Bool) -> Void
func pullFirebaseDataASYNC() {
self.pullFirebaseDataFunction() { (success) -> Void in
if success {
// Perform all other functions and carry on as normal...
Firebase function may look like...
func pullFirebaseDataFunction(completionHandler: #escaping CompletionHandler) {
let refUserData = DBProvider.instance.userDataRef
refUserData.observeSingleEvent(of: .value, with: { snapshot in
if let dictionary = snapshot.value as? [String: AnyObject] {
self.userCurrentScore = dictionary["UserScore"] as! Int
completionHandler(true)
}
})
}
Then when you segue the information across...
In ProfileVC
Create 2 properties
var containerVC: ContainerVC!
var userCurrentScore = Int()
Include the below function in ProfileVC...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ProfileToContainerSegue" {
let destination = segue.destination as! ContainerVC
containerVC = destination
containerVC.userCurrentScore = userCurrentScore
}
}
In ContainerVC create a property
var userCurrentScore = Int()
Ways to improve could be an error message to make sure all the information is pulled from Firebase before the user can continue...
Then the information can be segued across the same way as above.
Try instantiation, first embed a navigation controller to your first storyboard, and then give a storyboardID to the VC you are going to show.
let feedVCScene = self.navigationController?.storyboard?.instantiateViewController(withIdentifier: "ViewControllerVC_ID") as! ViewController
feedVCScene.scoreToChange = current_Score // scoreToChange is your local variable in the class
// current_Score is the current score of the user.
self.navigationController?.pushViewController(feedVCScene, animated: true)
PS:- The reason why instantiation is much healthier than a modal segue storyboard transfer , it nearly removes the memory leaks that you have while navigating to and fro, also avoid's stacking of the VC's.
Before I begin, let me say that I have taken a look at a popular post on the matter: Passing Data between View Controllers
My project is on github https://github.com/model3volution/TipMe
I am inside of a UINavigationController, thus using a pushsegue.
I have verified that my IBAction methods are properly linked up and that segue.identifier corresponds to the segue's identifier in the storyboard.
If I take out the prepareForSegue: method then the segue occurs, but obviously without any data updating.
My specific error message is: Could not cast value of type 'TipMe.FacesViewController' (0x10de38) to 'UINavigationController' (0x1892e1c).
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
if segue.identifier == "toFacesVC" {
let navController:UINavigationController = segue.destinationViewController as! UINavigationController
let facesVC = navController.topViewController as! FacesViewController
facesVC.balanceLabel.text = "Balance before tip: $\(balanceDouble)"
}
}
Below is a screenshot with the code and error.
side notes: using Xcode 6.3, Swift 1.2
A couple of things:
1: change your prepareForSegue to
if segue.identifier == "toFacesVC" {
let facesVC = segue.destinationViewController as! FacesViewController
facesVC.text = "Balance before tip: $\(balanceDouble)"
}
2: add a string variable to your FacesViewController
var text:String!
3: change the FacesViewController viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
balanceLabel.text = text
}
The reasons for all the changes: the segue destinationViewController is the actual FacesViewController you transition to -> no need for the navigationController shenanigans. That alone will remove the "case error", but another will occur due to unwrapping a nil value because you try to access the balanceLabel which will not have been set yet. Therefore you need to create a string variable to hold the string you actually want to assign and then assign that text in the viewDidLoad - at the point where the UILabel is actually assigned.
Proof that it works:
4: If you want display two decimal places for the balance you might change the String creation to something like (following https://stackoverflow.com/a/24102844/2442804):
facesVC.text = String(format: "Balance before tip: $%.2f", balanceDouble)
resulting in: