Why is add button causing a crash? - ios

What I'm trying to do is have a button that will pull the information from UIPickerviewer that will then add that to an array but when the program runs and you hit the button it crashes. Quite new to programming in swift... Would like a better explanation.
import UIKit
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
#IBOutlet weak var PickerView: UIPickerView!
#IBOutlet var list: [UILabel]!
#IBAction func add(_ sender: UIButton) {
var shift = true
}
#IBOutlet weak var label: UILabel!
var deviceList = [""]
var shift = false
override func viewDidLoad() {
super.viewDidLoad()
label.text = "Device List"
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
let devices = ["Mobile Phone","Microwave","Heater","Fridge","Router","Drill"]
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return devices[row]
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return devices.count
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
label.text = devices[row]
if(shift == true){
deviceList.append(label.text!)
print(deviceList)
shift = false
}
}
}

If you're getting a crash when you tap on your button I assume there is some conflicting assignments with the IBOutlet events referencing. The reason being is because your add(_:) action method isn't doing anything but creating a new boolean property called shift, and even though it's the same name as the class property shift (self.shift), that actually shouldn't cause a crash. Sometimes in interface builder you can have an outlet that you accidentally assigned more than one event, and that has usually caused a crash in my experience if you deleted one of the events in your code. For example: say you created a event for your button (like add(_:)), and then forgot and accidentally created a second one. You then realized your mistake and deleted the code for the second event, but didn't know you had to remove the event outlet. Interface builder will allow you to make this mistake, but will cause the app to crash when you tap the button, because it's trying to call an event that doesn't exist anymore.
If you want to check if you have any unnecessary event assignments through IB, control+click on the button in your documents outline on the left side of the screen using Interface Builder.
You'll see the referencing outlets listed under the "Referencing Outlets" header. You can remove any unnecessary ones by clicking the little "X" to the top left. You can also tell if the outlet is actually connected, by checking if it has a little filled circle to the right (this means it's connected). Here is an example, in this case I created an event call to a method called tryAgain: which I then removed. It causes a crash when I tap the button.
If I click the "X" button and remove the tryAgain: event. The app will work when the button is clicked.
I'm not certain this is your issue, but based on what you've shared it sounds likely to me. Hope this helps. I would recommend actually executing some code in your add(_:) method, however.

Nothing about the IBAction you posted should be causing a crash. You do need to get rid of the word "var" in the body of the function so that it changes the state of your instance variable rather than setting a local variable that never does anything, but that won't cause a crash.
That said, your "label" outlet is declared as an implicitly unwrapped optional, which is normal for outlets:
#IBOutlet weak var label: UILabel!
If the outlet is not connected to anything, though, then any time you try to read from or write to the label outlet or a property of the label, you'll crash if label is nil.
Check to see if you outlet is hooked up correctly. If it's not, then I would expect you to crash when you operate your picker. (Not when you click the add button.)

Related

Reading value of UITextField Swift 3

In my app, I have a UITextField and UIPickerView acting as dropdown, when user clicks on text field, he has to select his gender (Male or Female. I just want to read the value / string which user has selected in text field.Could some one please help me here
#IBAction func textChange(_ sender: UITextField) {
if userGenderTextField (Something has to here, Not sure what exactly)== "Male" {
print("Hello Sir")
} else {
print("Hello Madam")
}
}
You just have to use the .text method of UITextField
#IBAction func textChange(_ sender: UITextField) {
if sender.text == "Male" {
print("Hello Sir")
} else {
print("Hello Madam")
}
}
If you have several text fields connected to the same IBAction, you also need to check which text field called the function.
This sounds like a round-a-bout way of doing it.
Presumably at some point you're detecting which item in the UIPickerView the user selects and setting the text on the UITextField?
If so, why not store this value as a property. Then you always have access to it.
It doesn't seem like the best idea to have to inspect the text value of a UITextField when you want to know the state of play.
First of all, create a uIPickerView programmatically and set its dataSource and delegate properties to some objects that conform to the UIPickerViewDataSource and UIPickerViewDelegate respectively. The best approach is to have the object that controls the UITextField and UIPickerView conform to both these to protocols since you will be needing direct access to your textField and pickerView.
UIPickerViewDataSource is to control the data that is displayed in the picker.
UIPickerViewDelegate is to receive events that happen on the picker (e.g when the user selects an option).
Once you have your pickerView ready and set up you have your pickerView as the inputView of your textField
textField.inputView = pickerView
this way, when someone clicks on the textField, instead of presented the keyboard, you will be presented with you pickerView complete with your data.
On UIPickerViewDelegate there is a function called:
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int)
that gets called everytime you select an option on the pickerView.
Inside that function you can set the text of your textField and have it accessible elsewhere:
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
textField.text = yourData[component][row]
}
In if statement you put textfieldname.text== male print male else female

Drop-down style UIPickerView?

I was wondering how to get a UIPickerView to slide up from the bottom of the screen after tapping on a drop-down style button. Like in the image below:
I've run into this type of picker views a lot in the apps that I use regularly, so honestly I was expecting to easily find this picker view by setting the UIPickerView's style property, or something like that. Is this even a UIPickerView or do I have to create this kind of view manually?
One way of doing this is to have a normal UITextField and then assign a UIPickerView as the inputView of that textfield. That way, instead of a keyboard appearing when you tap your textfield, you get your pickerview.
Example
First declare a normal UIPickerView instance:
let yourPicker = UIPickerView()
and an outlet to a UITextField:
#IBOutlet weak var yourTextField: UITextField!
In your viewDidLoad you tie the elements together
yourPicker.delegate = self
yourPicker.dataSource = self
yourTextField.inputView = yourPicker
And then you need to implement the required methods of UIPickerViewDelegate and UIPickerViewDataSource
Finally. In pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) you update the value of your textfield:
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
yourTextField.text = yourDataArrayUsedInThePicker[row]
}
Read more
Description of inputView
https://developer.apple.com/library/content/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/InputViews/InputViews.html
A way better explaination than mine:
Show UIPickerView text field is selected, then hide after selected
Hope that helps you.

Manage a UIPickerView from an External Class - Using Swift

I cannot seem to find the connection to have an external class manage a view in a ViewController. I'm new to iOS and have spent considerable looking for a solution. Simple Example:
Subclass of UIPickerView
I create a file that is a subclass of UIPickerView and have it conform to the PickerView delegate and datasource.
class MyPickerView: UIPickerView, UIPickerViewDelegate, UIPickerViewDataSource {
//In here I conform to all the required methods...no problems there
}
Main View Controller with Outlet for the PickerView
In my MainViewController, I create an outlet for my picker view. Also, in the StoryBoard I hookup the "custom class" for my Picker View to MyPickerView above.
class MainViewController: UIViewController {
#IBOutlet weak var myPickerView: UIPickerView!
override func viewDidLoad() {
//how do I hookup my picker view class
}
}
My Questions:
How do I tell my MainViewController that my subclass "MyPickerView" is managing the picker view for it?
How did I enable communication between the subclass and the view controller?
---------------------
UPDATE: FINAL SOLUTION Incorporating #Oscar's Answer
#Oscar's suggestion below was great. To clarify, I wanted my PickerView subclass to be the UIPickerView Delegate because the Picker will always have the same UI and there are many PickerView delegate methods for UI. (attributedTitleForRow, widthForComponent, rowHeightForComponent, etc) I don't want to call those delegate methods in every ViewController that uses this PickerView.
Now, when the PickerView "didSelectRow" is called, we need to notify our ViewController and pass the value that was selected. To get this to work, I used a Protocol. (summarized below) This topic took me a while to learn, but is crucial, so I suggest spending time with Protocols & Delegation if this doesn't make sense.
Create a protocol in the PickerView with a func that will be used to talk to ViewControllers that present this PickerView:
protocol MyPickerViewProtocol {
func myPickerDidSelectRow(selectedRowValue:Int?)
}
In the ViewController presenting the PickerView, conform to your PickerView protocol. By doing so, you will have to place the func myPickerDidSelectRow somewhere in your ViewController:
class MyViewController: MyPickerViewProtocol {
func myPickerDidSelectRow(selectedRowValue:Int?) {
//do stuff to update your ViewController
}
}
#Oscar's answer below will hookup the picker view to your view controller, but there's one last thing. In order for the PickerView to talk back, you will want a property in your PickerView, that is a reference to the view controller it's contained in. Here's the PickeView and ViewController classes in perspective:
//PickerView Subclass ------------------
protocol MyPickerViewProtocol {
func myPickerDidSelectRow(selectedRowValue:Int?)
}
class MyPickerView: UIPickerView {
//Note: this var is of type your Picker protocol above. Because the ViewController will conform to the protocol, this var will be the reference (or the pointer) to the protocol func you implement in your ViewController...which is myPickerDidSelectRow
var propertyThatReferencesThisViewController:MyPickerViewProtocol?
}
//ViewController Class ----------------
myPicker = MyPickerView()
myPickerView.dataSource = myPicker //note: myPickerView is the outlet of type UIPickerView in your ViewController
myPickerView.delegate = myPicker
//HERE'S THE PROPERTY from our PickerView subclass that will point to this ViewController's protocol methods that we implemented. From the MyPickerViewProtocol
myPicker.propertyThatReferencesThisViewController = self
Now when a row is selected in our PickerView, let's tell the ViewController using our property: propertyThatReferencesThisViewController
class MyPickerView: UIPickerView {
//This Property points to the ViewController conforming to the protocol. This property will only be able to access the stuff you put in the protocol. It won't access everything in your ViewController
var propertyThatReferencesThisViewController:MyPickerViewProtocol?
//didSelectRow UIPickerView Delegate method that apple gives us
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
//get your picker values that you need
let theRowValue = someArray[row]
propertyThatReferencesThisViewController?.myPickerDidSelectRow(theRowValue)
//the ViewController func will be called passing the row value along
}
}
Subclass Pickerview
class MyPickerView: UIPickerView, UIPickerViewDataSource, UIPickerViewDelegate {
var oficinas = ["oficina 1", "Oficinas 2", "Oficina 3"]
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return oficinas.count
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return oficinas[row]
}
}
Main View Controller with Outlet for the PickerView
class MainViewController: UIViewController {
#IBOutlet weak var myPickerView: UIPickerView!
var pickerOficinas: MyPickerView!
override func viewDidLoad() {
//how do I hookup my picker view class
pickerOficinas = MyPickerView()
myPickerView.delegate = pickerOficinas
myPickerView.dataSource = pickerOficinas
}
}
I think you may have got hold of the wrong end of the stick!
Why do you want to make the picker its own delegate? The point of having a delegate is that it can tell its delegate what has been selected etc.
I would think that what you should be doing is making your view controller conform to UIPickerViewDelegate and make it the delegate of the picker and put the logic for whatever you want to happen when an item is picked in those delegate methods. I can't see any other way of 'telling' your view controller about the picker.
Also, if you reference to your picker is weak, then unless you are holding a strong reference to it somewhere else, at all times (eg it is part of the view hierarchy) it will be deallocated.

Send button, which opens UIPickerView to UIPickerView's selectRow function

I have two buttons, which opens one UIPickerView
#IBOutlet weak var convertFromButton: UIButton!
#IBOutlet weak var convertToButton: UIButton!
I need picker's selectRow function to know, which button user press to open it and to change title of this button. So, I need function like this, but I don't know how to send button to this function.
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int, button: UIButton) {
button.setTitle(converter.convertTypes[row], forState: UIControlState.Normal)
typePicker.hidden = true;
}
Or do I need separate picker for each button?
You can use additional variable to store the latest sender button which was used to open a picker and check its value in the pickerView:didSelectRow:

Using UIPickerView with multiple components in swift

Hi there I have been trying to get this working for ages with no success so apologies if this seems easy for you guys.
I want to use a 3 wheel UIPickerView - The 1st wheel didSelectRow value will be used to change the array for the remaining two wheels but they will be the same as it is a conversion app.
It seems to throw me an error up when populating the wheels saying that Anyobject is not identical to String. I cannot see anything wrong but I know it is something basic I have missed.
Any pointers would be much appreciated.
Motty.
// ViewController.swift
// picker test
import UIKit
class ViewController: UIViewController, UIPickerViewDelegate {
#IBOutlet weak var picker1Label: UILabel!
#IBOutlet weak var picker2Label: UILabel!
#IBOutlet weak var bigPicker: UIPickerView!
var wheelContents = []
var length = ["metres","feet","yards","inches","mm","cm","miles"]
var volume = ["m3","US Gall","Imp Gall","Barrels", "cubic FT","litres"]
var conType = ["length","volume"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
wheelContents = [conType,length,length]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//For main selection of type of conversion
// returns the number of 'columns' to display.
func numberOfComponentsInPickerView(bigPicker: UIPickerView) -> Int{
return wheelContents.count
}
// returns the # of rows in each component..
func pickerView(bigPicker: UIPickerView, numberOfRowsInComponent component: Int) -> Int{
return wheelContents[component].count
}
func pickerView(bigPicker: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String!{
return wheelContents[component][row]
}
}
You need to tell Swift that your wheelContents array is an array of array of String:
var wheelContents:[[String]] = []
If you don't explicitly give wheelContents a type, Swift infers it to be NSArray which is what is giving you problems.
You need to say swift, that your wheelContents is an array of String arrays:
var wheelContents:[[String]] = []
Also you should set the delegate of your pickerView to self because you handle the delegate in your class. Otherwise the functions won't work properly:
//In the viewDidLoad method
bigPicker.delegate = self
Since you declared wheelContents like this wheelContents = [], without specifying its elements type, the compiler automatically infers that it is array of AnyObjects aka [AnyObject].
That's the reason why when you are returning wheelContents[component].count it generates an error: at that moment the compiler is expecting a String! but you are providing an AnyObject.
It's a really easy fix, you should just specify what the content of the array it is going to be when you declare it. (it's an array of arrays of strings aka [[String]])
You need to set the datasource of pickerView along with the delegate in viewDidLoad
pickerView.dataSource = self

Resources