I am trying to link to another view controller by pressing a button which i have named 'find'. The issue is with the pickerView. i want the 'service' selected from the pickerView to determine which page you end up on. For example, you select 'bars' in the pickerView, you click the 'find' button and it takes you to the bars view controller page.
Here is my code which controls the picker and the find button.
import UIKit
class ViewController: UIViewController, UIPickerViewDelegate {
#IBOutlet weak var SelectedService: UILabel!
#IBAction func Find(sender: AnyObject) {
}
#IBOutlet weak var ItemLabel: UILabel!
var services = ["Cafe","Coffee Shops","Bar","Takeaway","Sunday Roast","Shoe Mender","Craft Shops","Electrical"]
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.
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int{
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int{
return services.count
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String!{
return services[row]
}
}
I am new to coding with swift so please could you explain your answer in a simple way.
Thank you very much
Shaun
In #IBAction func Find() you could have switch statement where you check which item from UIPickerView was selected, and based on that you can present desired view controller like this:
let cafeViewController: CafeViewController = CafeViewController()
self.presentViewController(cafeViewController, animated: true, completion: nil)
But are you sure you need it's own view controller for every item? They'll probably look exactly the same, only presented data will be different... so you could use just one view controller, send picked item in func prepareForSegue() and according on that prepare data in viewDidLoad() in that view controller.
Or at least try to reuse as much as you can creating some base class for your view controller and then create subclass for each service.
Related
I am new to Xcode/Swift and working on an assignment for which I need some help. I have a simple program that displays a list of names when started. I want to take a new name, from the UITextField, and add it to the data for the picker after the item that is currently selected. The new added name should be shown in the picker as the selected item (so the previously selected item would be immediately above it on the picker’s display at that point). I have the program working thus far, however, my "picker.reloadAllComponents()" function seems to be breaking something. When I leave this in place, none of the data in the picker is being shown. When I remove it, I don't know if my insertButton action is working. Where can I refresh the picker data such that the picker data is being seen in my simulator? Thanks.
import UIKit
class ViewController: UIViewController,
UIPickerViewDelegate, UIPickerViewDataSource {
var characterNames = [
"Luke", "Leia", "Han", "Chewbacca", "Artoo",
"Threepio", "Lando"]
#IBOutlet weak var userInput: UITextField!
#IBOutlet weak var picker: UIPickerView!
#IBAction func selectButton(_ sender: UIButton) {
let row = picker.selectedRow(inComponent: 0)
let selected = characterNames[row]
let title = "You selected \(selected)!"
let alert = UIAlertController(
title: title,
message: "Thank you for choosing",
preferredStyle: .alert)
let action = UIAlertAction(
title: "You're welcome",
style: .default,
handler: nil)
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
#IBAction func insertButton(_ sender: UIButton) {
characterNames.insert(userInput.text!, at: picker.selectedRow(inComponent: 0))
}
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.
}
// MARK:-
// MARK: Picker Data Source Methods
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView,
numberOfRowsInComponent component: Int) -> Int {
return characterNames.count
}
// MARK: Picker Delegate Methods
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
picker.reloadAllComponents()
return characterNames[row]
}
}
Don't call picker.reloadAllComponents() from within the titleRorRow method as I think this may be invoking an infinite loop - this delegate method should return the title for that row only, as your second line is doing correctly. So remove it from there.
Instead, place it in your insertButton method after you have updated the source. This will cause the data to be reloaded, and the data source methods called again to repopulate the picker.
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return characterNames[row]
}
#IBAction func insertButton(_ sender: UIButton) {
characterNames.insert(userInput.text!, at: picker.selectedRow(inComponent: 0))
picker.reloadAllComponents()
}
I am building a simple app that has a PickerView with 3 selections that should link to 3 viewControllers each named FirstViewController, SecondViewController and ThirdViewController.
But I am getting this error
> NSInvalidArgumentException', reason: 'Receiver (<Scroller.ViewController: 0x7ff7b5e0b670>) has no segue with identifier 'SecondSegue'
I have three options in the picker and a button to go to that selection's viewController:
My code below works on first loading then crashes
import UIKit
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
#IBAction func continueButtonPressed(_ sender: AnyObject) {
self.performSegue(withIdentifier: "\(chosenState)Segue", sender: nil)
print("no chance")
}
#IBOutlet weak var pickerView: UIPickerView!
var pickerData = ["First","Second","Third"]
var chosenState = ""
override func viewDidLoad() {
super.viewDidLoad()
print("Help")
pickerView.delegate = self
pickerView.dataSource = self
// 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.
}
// The number of columns of data
// The number of rows of data
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return pickerData.count
}
// The data to return for the row and component (column) that's being passed in
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return pickerData[row]
}
//Called when the user changes the selection...
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
chosenState = pickerData[row]
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
}
can anyone help me understand how to fix this ?
Have I named the Segues ok? They are all named the same - is that correct?
You have to set the segue identifier to second view controller as SecondSegue
It seems your SecondSegue to your second UIViewController has not been named properly.
Be careful, it is case sensitive, it must be named precisely SecondSegue. And after having changed the name, don't forget to validate your input with the return key.
I'm creating a temperature converter. When I run the application; enter a temperature, select which conversion and click convert. An error comes up.
THE ERROR IS: EXC_BAD_INSTRUCTION (code = EXC_I386_INVOP, subcode=0x0)
This is my code for ViewController:
import UIKit
class ViewController: UIViewController,UIPickerViewDataSource,UIPickerViewDelegate {
#IBOutlet weak var orginalValue: UITextField!
#IBOutlet weak var convertFrom: UIPickerView!
let pickerData = ["Celsius", "Fahrenheit"]
override func viewDidLoad() {
super.viewDidLoad()
convertFrom.dataSource = self
convertFrom.delegate = self
}
#IBOutlet weak var labelConvertFrom: UILabel!
#IBOutlet weak var convertTo: UIPickerView!
#IBOutlet weak var labelConverTo: UILabel!
#IBOutlet weak var answer: UILabel!
#IBAction func convertButton(sender: AnyObject) {
let a = Double(orginalValue.text!)
let tempConvertM = TempConvert(temp: a!)
answer.text = String(tempConvertM.convert())
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return pickerData.count
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return pickerData[row]
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
labelConvertFrom.text = pickerData[row]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Here is where I'm getting the error.
TempConverterModel.swift
import Foundation
extension ViewController{
class TempConvert{
var temp:Double
var view = ViewController()
init (temp:Double){
self.temp = temp
}
func convert()->Double{
if(view.labelConvertFrom.text == "Celsius"){ -->ERROR IS HIGHLIGHTED HERE <--
view.labelConverTo.text = "Fahrenheit"
return (temp-32)/1.8000; //fahrenheit formula
}
else{
view.labelConverTo.text = "Celsius"
return (temp*1.8000)+32; //celsius formula
}
}
}
}
I don't know what I'm doing wrong. I want to check the text in labelConvertFrom and check if it equals to "Celsius". IF it does not then return answer.
I would really appreciate anyones help. Thank you!
As par pointed out, you should be removing the enclosing extension ViewController { } and have your TempConvert as a separate class.
Also, instead of trying to access the ViewController's instance variables in TempConvert, you should be doing the comparisons in your convertButton() method in ViewController class itself and call the appropriate conversion method in TempConvert class.
A better approach is to have a stored property for "Celsius" and a computed property for "Fahrenheit" within your ViewController class. You can refer this link for Properties in Swift language
The problem broadly stems from this line in class TempConvert:
var view = ViewController()
Here you are initializing an empty view controller. The outlets you've defined such as labelConvertFrom are not hooked up to anything, so when you try to deference them here:
view.labelConvertFrom.*text
you crash (specifically, you crash where I put the * character). The reason you crash is because at that point, labelConvertFrom is nil.
To get this to work right, you'll need to initialize the ViewController using the initWithNibName:bundle: method, passing the correct nib filename and bundle id (which is probably just NSBundle.mainBundle()).
Doing this will allow your outlets to be hooked up properly and then they won't be nil when you try to use them.
I have created a UIPickerView which has an array of services inside. I have used the didSelectRow to display my selected service below the UIPickerView. That's all fine and works perfectly.
I want the service which as been selected to take me to another view controller named that service. For example, Coffee shops as been selected in the Picker, "coffee shops" is displayed below the picker and you click a button which will bring you to a page which displays the given information about coffee shops.
#Pbush25 thanks for the quick reply. Please would you take a look at my code and tell me where I am going wrong as I still can't get it to work.
import UIKit
class ViewController: UIViewController, UIPickerViewDelegate {
#IBOutlet weak var SelectedService: UILabel!
#IBAction func Find(sender: AnyObject) {
}
#IBOutlet weak var ItemLabel: UILabel!
var services = ["Cafe","Coffee Shops","Bar","Takeaway","Sunday Roast","Shoe Mender","Craft Shops","Electrical"]
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.
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int{
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int{
return services.count
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String!{
return services[row]
}
}
As #JAL said, there is a method for the UIPickerViewDelegate that I belive is didSelectRow or something to that extent, which will give you the index path of the row that was selected. When your button is pressed to go to the next screen, all you have to do in your prepareForSegue method is get the element at that row and use a switch statement to determine which new view controller to activate the segue on based on what value you receive.
EDIT: Added code for clarity.
//save the picked data to a variable
func pickerView(pickerView: UIPickerView!, didSelectRow row: Int, inComponent component: Int)
{
selection = "\(pickerViewData[row])"
}
//use this variable to decide which segue to take
func prepareForSegue(sender: UIStoryboardSegue){
switch selection {
case: "Coffee Shops"
performSegueWithIdentifier("goToCoffeeShops", sender: self)
case: "Some other selection"
performSegueWithIdentifier("someOtherSegue", sender: self)
}
}
I'm not sure I set this up correctly: you can navigate between calculator view controller to settings view controller and back using the nav bar, however...upon setting either the picker or the segmented control, the data isn't passed back to the calculator view controller via the code below. I think this is because I don't have a corresponding segue on my storyboard pointing back to the calculator view controller. I would do that, but why doesn't the back button show on the story board on settings view controller? If it did I could just control drag back to calculator view controller? I must be missing something?
Also I don't just want the picker/segmented control data to be used once, i.e. I don't want to reset when the user goes back to settings viewcontroller, I want these choices saved as long as the app is open. How do I do that?
The only other existing answers I could find were in Objective C
code:
class settingsViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate {
let titleData = TitleData()
var selectedCounty = String?("Allegany")
var priceMax = Float(1000000)
var priceMin = Float(0)
var payoffMax = Float(1000000)
var payoffMin = Float(0)
#IBOutlet weak var countyPicker: UIPickerView!
#IBOutlet weak var segmentedControl: UISegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
countyPicker.delegate = self
countyPicker.dataSource = self
}
#IBAction func indexChanged(sender: UISegmentedControl) {
switch segmentedControl.selectedSegmentIndex
{
case 0:
priceMax = 500000.0
priceMin = 0.0
case 1:
priceMax = 1000000.0
priceMin = 500000.0
case 2:
priceMax = 2000000.0
priceMin = 1000000.0
case 3:
priceMax = 5000000.0
priceMin = 2000000.0
default:
break
}
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return titleData.mdCounties.count
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
selectedCounty = titleData.mdCounties[row].name
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
return titleData.mdCounties[row].name!
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var destViewController: FirstViewController = segue.destinationViewController as! FirstViewController
destViewController.selectedCounty = selectedCounty
destViewController.newPriceMax = priceMax
destViewController.newPriceMin = priceMin
destViewController.newPayoffMax = payoffMax
destViewController.newPayoffMin = payoffMin
}
}
This is a FAQ on stack overflow. It can be generalized as "how do I pass data between ViewControllers." It isn't specific to Swift. The techniques are identical in Objective-C and swift. The only thing that changes is the language you use to implement it.
The most global, flexible way to do this is to create a data container singleton that holds your app state. Set up your singleton to save app state to a file (user defaults, or to a file in your app's documents directory.
Any time you want to save app data, save it to the singleton. Any time you want to read app data, read it from the singleton.
If you have a more specific case where you want to pass data between one view controller and another one that you segue to, you can pass a data object to the destination view controller in prepareForSegue. Then you can change the data object in the second view controller, and when you return from the segue, the data will be changed in the data object.