I am building a very simple swift application that uses two view controllers - "ViewControler" and "AnimalChooserViewControler". First has a simple label and a toolbar with one item, which transfers the user to the second screen. In the second, have custom picker. The whole purpose of the app is to display whatever the user has selected from the second view controler to the first one using the output UILabel. I have to mention that I am usong xCode 6.4 (ios8)
My problem is when I try to cast the presentedViewController as! ViewController the app crashes with "fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)" exceptions. How to solve this problem, any suggestions?
Here is my code in the ViewControler:
class ViewController: UIViewController {
#IBOutlet weak var outputLabel: UILabel!
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 displayAnimal(choosenAnimal: String, withSound choosenSound: String, fromComponent choosenComponent: String){
self.outputLabel.text = "You changed \(choosenComponent) (\(choosenAnimal)(and the sound \(choosenSound))"
}
}
And this is my code in AnimalChooserViewControler
class AnimalChooserViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate{
let kComponentCount: Int = 2
let kAnimalComponent: Int = 0
let kSoundComponent: Int = 1
var animalNames: [String] = []
var animalSounds: [String] = []
var animalImages: [UIImageView] = []
#IBAction func dismisAnimalChooser(sender: AnyObject) {
dismissViewControllerAnimated(true, completion: nil)
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return kComponentCount
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if component == kAnimalComponent{
return animalNames.count
} else {
return animalSounds.count
}
}
func pickerView(pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusingView view: UIView!) -> UIView {
if component == kAnimalComponent {
let choosenImageView: UIImageView = animalImages[row]
let workarroundImageView: UIImageView = UIImageView(frame: choosenImageView.frame)
workarroundImageView.backgroundColor = UIColor(patternImage: choosenImageView.image!)
return workarroundImageView
} else {
let soundLabel: UILabel = UILabel(frame: CGRectMake(0, 0, 100, 32))
soundLabel.text = animalSounds[row]
return soundLabel
}
}
func pickerView(pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
return 55.0
}
func pickerView(pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
if component == kAnimalComponent{
return 75.0
} else {
return 150.0
}
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let initialView : ViewController = presentedViewController as! ViewController
if component == kAnimalComponent{
let choosenSound: Int = pickerView.selectedRowInComponent(self.kSoundComponent)
initialView.displayAnimal(animalNames[row], withSound: animalSounds[choosenSound], fromComponent: "the Animal")
} else {
let choosenAnimal: Int = pickerView.selectedRowInComponent(kAnimalComponent)
initialView.displayAnimal(animalNames[choosenAnimal], withSound: animalSounds[row], fromComponent: "the Sound")
}
}
override func viewDidLoad() {
super.viewDidLoad()
animalNames = ["Mouse","Goose","Cat","Dog","Snake","Bear","Pig"]
animalSounds = ["Oink","Rawr","Sss","Meow","Honk","Squeak"]
animalImages = [UIImageView(image: UIImage(named: "mouse.png")),
UIImageView(image: UIImage(named: "goose.png")),
UIImageView(image: UIImage(named: "cat.png")),
UIImageView(image: UIImage(named: "dog.png")),
UIImageView(image: UIImage(named: "snake.png")),
UIImageView(image: UIImage(named: "bear.png")),
UIImageView(image: UIImage(named: "pig.png"))]
preferredContentSize = CGSizeMake(340,380)
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let initialView: ViewController = presentedViewController as! ViewController
initialView.displayAnimal(animalNames[0], withSound: animalSounds[0], fromComponent: "nothing yet...")
}
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.
}
*/
}
I found what was causing the exception. I have used "presentedViewController" instead of "presentingViewController". Silly mistake but it took me a good hour to find it out :)
Related
I'm relatively new into swift and I'm still struggling with some basics. But well thank you all fo helping me in this. Ok when a UITableViewCellis selected a PopUpViewController is presented with a datepicker so the user can select a desire date and when dissmisButtonis pressed the current view is dismissed and as soon that happens I want the selected value to show on the label embedded on the selected UITableViewCell.
Code on PopUpViewController
import UIKit
class PopUpColchonesViewController: UIViewController {
var tipoColchonArray = [String](arrayLiteral: "Individual", "Matrimonial", "King Size")
#IBOutlet weak var tipoColchonLb: UILabel!
#IBOutlet weak var tipoColchonPicker: UIPickerView!
#IBOutlet weak var guardarTipoColchon: UIButton!
var tipoSeleccionado : String?
override func viewDidLoad() {
super.viewDidLoad()
tipoColchonPicker.delegate = self
tipoColchonPicker.dataSource = self
// Do any additional setup after loading the view.
}
#IBAction func guardarTipo(_ sender: Any) {
NotificationCenter.default.post(name: .guardarTipo, object: nil)
dismiss(animated: true, completion: nil)
}
}
extension PopUpColchonesViewController : UIPickerViewDelegate, UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return tipoColchonArray.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return tipoColchonArray[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let selectedItem = tipoColchonArray[row]
tipoSeleccionado = selectedItem
}
}
extension Notification.Name {
static let guardarTipo = Notification.Name(rawValue: "guardarTipo")
}
What I've try so far
var observer : NSObjectProtocol?
var cellServicio = UITableViewCell()
override func viewDidLoad() {
super.viewDidLoad()
servicioHogarTB1.delegate = self
servicioHogarTB1.dataSource = self
servicioHogarTB1.register(UINib(nibName: "ServicioCell", bundle: nil), forCellReuseIdentifier: "servicioCell")
servicioHogarTB1.separatorStyle = .none
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(handlePopupClosing), name: .guardarTipo, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
}
print(servicioSeleccionado)
}
#objc func handlePopupClosing (notification : NSNotification) {
let tipoVC = notification.object as? PopUpColchonesViewController
let index = servicioHogarTB1.indexPathForSelectedRow
if let cell = servicioHogarTB1.cellForRow(at: index!) as? ServicioCell {
cell.servicioTipoLb.text = tipoVC?.tipoSeleccionado
}
}
The results so far are error while unwrapping an optional value and the text on label is not shown.
Change your method like this and check ... hopefully it will work
#IBAction func guardarTipo(_ sender: Any) {
dismiss(animated: true, completion: {
NotificationCenter.default.post(name: .guardarTipo, object: nil)
})
}
In my EditVC, I performed segue from selected CellForRow At indexPath to editVC.
In Edit VC. I want to expect results of data return as it is IF there is no modification. However, issue is if I did not rechoose/reselect the row at pickerView again, it will auto return nil.
I want to have results of if User did not modify the data/specific data only, other data will remain the same.
This is the results of print statement.
// This results is perform correctly for what I want IF I RESELECT THE PICKERVIEW IN TEXTFIELD BUT WILL AMEND MY CATEGORY DATABASE AS WELL.
currentTransaction.categoryID : Optional(9)
selectedCategory.categoryID : 9
currentTransaction.categoryTypeID : Optional(1)
selectedCategory.categoryTypeID : 1
currentTransaction.categorytype.name = Optional("EXPENSE 9")
selectedCategory.name = EXPENSE 9
// If I never reselect the pickerview in textfield
currentTransaction.categoryID : Optional(0) /* Suppose to be 9 */
selectedCategory.categoryID : 0 /* Suppose to be 9 */
currentTransaction.categoryTypeID : Optional(0) /* Suppose to be 1 */
selectedCategory.categoryTypeID : 0 /* Suppose to be 1 */
currentTransaction.categoryType.name = Optional("") /* Suppose to be EXPENSE 9 */
selectedCategory.name = /* Suppose to be EXPENSE 9 */
class EditTransactionTableViewController: UITableViewController {
let realm = try! Realm()
var selectedTransaction : Transaction? //selectedTransaction From TransactionVC's CellForRow
var categories : Results<Category>!
var currentCategories : Results<Category>!
var selectedCategory = Category()
#IBOutlet weak var amountTF: UITextField!
//PickerView for keyboard
lazy var pickerView : UIPickerView = UIPickerView()
//Segment Control
#IBOutlet weak var categorySCoutlet: UISegmentedControl!
#IBOutlet weak var categoryTF: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
amountTF.text = selectedTransaction?.amount
categoryTF.text = selectedTransaction?.categoryType?.name
categorySCoutlet.selectedSegmentIndex = selectedTransaction?.categoryType?.categoryTypeID as! Int
loadData()
setupPicker()
}
#IBAction func categoryTypeSC(_ sender: UISegmentedControl) {
guard let type = CategoryType(id: sender.selectedSegmentIndex) else {
fatalError("error")
}
currentCategories = categories.filter("categoryTypeID == %#", type.rawValue)
categoryTF.text = currentCategories.first?.name
pickerView.reloadAllComponents()
// pickerView.selectRow(0, inComponent: 0, animated: true)
}
//MARK:- Edit Transaction Btn
#IBAction func editTransactionButtonTapped(_ sender: UIButton) {
if let currentTransaction = selectedTransaction {
try! realm.write {
currentTransaction.amount = amountTF.text!
//Success modification if got RESELECT row of category BUT will return "" if never reselect and cause categories list become chosen categoryID, name , and type
currentTransaction.categoryType?.categoryID = selectedCategory.categoryID
currentTransaction.categoryType?.categoryTypeID = selectedCategory.categoryTypeID
currentTransaction.categoryType!.name = selectedCategory.name
print("currenttransaction.categoryID : \(currentTransaction.categoryType?.categoryID)")
print("selectedCategory.categoryID : \(selectedCategory.categoryID)")
print("currenttransaction.categoryTypeID : \(currentTransaction.categoryType?.categoryTypeID)")
print("selectedCategory.categoryTypeID : \(selectedCategory.categoryTypeID)")
print("currenttranasction.categorytype.name = \(currentTransaction.categoryType?.name)")
print("selectedcategory.name = \(selectedCategory.name)")
}
}
DataManager.shared.transVC.tableView.reloadData()
dismiss(animated: true, completion: nil)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if touches.first?.view == view {
categoryTF.resignFirstResponder()
}
}
// MARK: - Data Manipulation
func loadData() {
categories = realm.objects(Category.self)
}
}
//MARK:- UIPICKERVIEW DELEGATE
extension EditTransactionTableViewController : UIPickerViewDelegate, UIPickerViewDataSource {
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return currentCategories.count
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return currentCategories[row].name
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
categoryTF.text = currentCategories[row].name
selectedCategory = currentCategories[row]
}
//MARK:- Picker Helper
func setupPicker() {
// currentCategories = categories.filter("categoryTypeID == %#", CategoryType.income.rawValue)
currentCategories = categories.filter("categoryTypeID == %#", selectedTransaction?.categoryType?.categoryTypeID)
categoryTF.inputView = pickerView
pickerView.delegate = self
pickerView.dataSource = self
categorySCoutlet.setTitle("Income", forSegmentAt: 0)
categorySCoutlet.setTitle("Expense", forSegmentAt: 1)
categorySCoutlet.addTarget(self, action: #selector(categoryTypeSC(_:)), for: .valueChanged)
}
}
I am trying to set my Button title from a selected row from uipickerview. However, I can't seem to find a valid solution.
I have set the variables to be global thus I can access the Selected Variable. However I cant seem to be able to pinpoint exactly when the user clicks the exit button and able to update my button title.
Things I have tried:
Global Functions ( couldn't work because you cant specify the button you want to change the name of )
Dispatchqueue.main.async - Did not update my title. The idea here is for it to constantly check for a string change.
The solution I found here is in Obj-C. I tried converting it to swift https://objectivec2swift.com/#/converter/code/ with no luck.
Set Title of UIButton from Selected Row of UIPickerView
#IBAction func CountryPickButton(_ sender: UIButton) {
Global.setPickerDataSource(data:["Test","test2","test3"])
Global.setPickerCompletionHandler(int:0)
let popOverVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "PickerViewPopUpId") as! PickerViewController
//CountryPickButtonDetector = sender as? UIButton
self.addChild(popOverVC)
popOverVC.view.frame = self.view.frame
self.view.addSubview(popOverVC.view)
popOverVC.didMove(toParent: self)
popOverVC.callback { value in
//CountryPickButton.title.text = value
sender.setTitle(value, for: UIControl.State)
}
}
import UIKit
var test = ""
class PickerViewController: UIViewController {
#IBOutlet weak var PickerView: UIPickerView!
var callback : ((String) -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
//self.view.backgroundColor = UIColor.black.withAlphaComponent(0.8)
PickerView.dataSource = self
PickerView.delegate = self
// Do any additional setup after loading the view.
}
#IBAction func ExitButton(_ sender: Any) {
switch Global.pickerCompletionHandler {
case 0:
callback?(Global.pickerResult ?? "")
Global.setProfileCountry(string:
Global.pickerResult ?? "")
default:
print("nothing")
}
self.view.removeFromSuperview()
}
}
extension PickerViewController: UIPickerViewDelegate, UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1 // can be more than 1 component like time/date/year
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return Global.pickerDataSource?.count ?? 1
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
//Global.pickerResult = Global.pickerDataSource?[row]
Global.setPickerResult(result: Global.pickerDataSource?[row] ?? "Test" )
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return Global.pickerDataSource?[row]
}
}
Globals
import Foundation
struct Global {
static var pickerDataSource:[String]? = nil
static var pickerResult:String? = nil
static var pickerCompletionHandler:Int? = 0
static var profileCountry:String? = nil
static func setPickerDataSource(data:[String]) {
Global.pickerDataSource = data
}
static func setPickerResult(result:String) {
Global.pickerResult = result
}
static func setPickerCompletionHandler(int: Int) {
Global.pickerCompletionHandler = int
}
static func setProfileCountry(string: String)
{
Global.profileCountry = string
}
}
I don't know what all that Global stuff is, the easiest solution might be to extend pickerCompletionHandler, but here is another (easy) one:
In PickerViewController add a callback passing a String value and no return value
var callback : ((String) -> Void)?
and call it in exitButton (please, please variable and function names are supposed to start with a lowercase letter)
#IBAction func exitButton(_ sender: Any) {
switch Global.pickerCompletionHandler {
case 0:
callback?(Global.pickerResult ?? "")
Global.setProfileCountry(string:
Global.pickerResult ?? "")
default:
print("nothing")
}
self.view.removeFromSuperview()
}
In countryPickButton (again: lowercase letter) set the callback and set sender to the real type. The callback sets the title
#IBAction func countryPickButton(_ sender: UIButton) {
Global.setPickerDataSource(data:["Test","test2","test3"])
Global.setPickerCompletionHandler(int:0)
let popOverVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "PickerViewPopUpId") as! PickerViewController
popOverVC.callback = { value in
sender.setTitle(value, for: .normal)
}
...
From "var myValue:[Double]..." and "var activeCurrency:Double = 0;" to the third pickerview code, I am not sure what to do in order to fix the "self.myValues.append((Url as? Double)!)" so that my app displays the currency amount. I have searched everywhere, and have no luck in finding anything. You are my last hope, please help. I'm still fairly new to coding, so I may not know the certain jargon.
import UIKit
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
var myCurrency:[String] = []
var myValues:[Double] = []
var activeCurrency:Double = 0;
//OBJECTS
#IBOutlet weak var textbox: UITextField!
#IBOutlet weak var pickerView: UIPickerView!
#IBOutlet weak var output: UILabel!
//CREATING PICKER VIEW
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return myCurrency.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return myCurrency[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
activeCurrency = myValues[row]
}
//BUTTON
#IBAction func action(_ sender: AnyObject) {
if (textbox.text != "") {
output.text = String(Double(textbox.text!)! * activeCurrency)
}
}
override func viewDidLoad() {
super.viewDidLoad()
//DATA GRABBING
let url = URL(string: "https://min-api.cryptocompare.com/data/all/coinlist")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print ("ERROR")
}
else {
if let content = data {
do {
let myJson = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
if let rates = myJson["Data"] as? NSDictionary {
for (Name, Url) in rates {
self.myCurrency.append((Name as? String)!)
self.myValues.append((Url as? Double)!)
}
}
}
catch {
}
}
}
if data != nil {
DispatchQueue.main.async { // Correct
self.pickerView.reloadAllComponents()
}
}
}
task.resume()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I have a problem in returning the value from a picker and comparing it to the value of an attribute. Basically, i have a Floor entity which has a attribute "floorNumber". From the picker I need to get the floor number and match it to the correct entity object so I can start assigning rooms to individual floors. I have implemented the delegate and the data source for the picker and looked at other questions on this site but I'm getting a bit confused.
Please have look at my implementation and please tell me what am I doing wrong. I have an optional "pickedFloor" which is nil even though it is being set and if I try to unwrap it it will crash it. Thank you for your help
class SetNumberOfRoomsPerFloor: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
//MARK: - Properties
#IBOutlet private weak var floorPicker: UIPickerView!
#IBOutlet private weak var numberOfRoomsPerFloor: UITextField!
private var managedObjectContext: NSManagedObjectContext!
internal var floorValue: Int16?
private var convertedFloorValues = [String]()
private var storedFloors = [Floors]()
private var pickedFloor: Int16?
private var roomNumberValue: Int16 {
get {
return Int16(numberOfRoomsPerFloor.text!)!
}
}
override func viewDidLoad() {
super.viewDidLoad()
managedObjectContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
floorPicker.delegate = self
floorPicker.dataSource = self
loadFloorData()
spinnerItems()
}
#IBAction func setTheFloors(_ sender: UIButton) {
if storedFloors.count > 0 {
if storedFloors.first?.floorNumber == pickedFloor {
storedFloors.first?.numberOfRooms = roomNumberValue
print("\(storedFloors.first?.floorNumber) + \(storedFloors.first?.numberOfRooms)")
}
}
}
#IBAction func nextStep(_ sender: UIButton) {}
private func loadFloorData() {
let floorRequest: NSFetchRequest<Floors> = Floors.fetchRequest()
do {
storedFloors = try managedObjectContext.fetch(floorRequest)
} catch {
print("could not load data from core \(error.localizedDescription)")
}
}
private func spinnerItems() {
for i in 0...floorValue! - 1 {
convertedFloorValues.append(String(i))
}
}
public func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return convertedFloorValues.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return convertedFloorValues[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let selection = Int16(convertedFloorValues[row]) // not sure I have to do this part.
pickedFloor = selection
}
}
Inside this function, you should do this
private func spinnerItems() {
for i in 0...floorValue! - 1 {
convertedFloorValues.append(String(i))
}
if convertedFloorValues.count >= 1 {
pickedFloor = Int16(convertedFloorValues[0])
}
}
Like I said in the comments, didSelectRow never gets called if you don't actually move the picker view.
If the user hasn't selected anything in pickerview then pickedFloor will be nil.Try this.
#IBAction func setTheFloors(_ sender: UIButton) {
if storedFloors.count > 0 {
if storedFloors.first?.floorNumber == convertedFloorValues[floorPicker.selectedRow(inComponent: 0)] {
storedFloors.first?.numberOfRooms = roomNumberValue
print("\(storedFloors.first?.floorNumber) + \(storedFloors.first?.numberOfRooms)")
}
}
}