I'm relatively new to programming and this is my first attempt at developing an iOS without following a tutorial, so please bear with me.
I have a custom table view called 'CupboardViewController' that returns four string labels based on a custom UITableViewCell class called 'ItemTableViewCell'. I want to be able to show those four labels in a separate view controller called 'detailViewController' when a user clicks on a table item.
This is the code I have but it's crashing with no obvious error message when the segue is called. Please help!
CupboardViewController
override func prepareForSegue(segue: UIStoryboardSegue,
sender: AnyObject!) {
// sender is the tapped `UITableViewCell`
let cell = sender as! ItemTableViewCell
let indexPath = self.tblItems.indexPathForCell(cell)
// load the selected model
let titleToPass = itemMgr.items[indexPath!.row].name
let detail = segue.destinationViewController as! detailViewController
// set the model to be viewed
detail.titleToPass = titleToPass
}
}
detailViewController
#IBOutlet weak var titleDetailLabel: UILabel!
#IBOutlet weak var qtyDetailLabel: UILabel!
#IBOutlet weak var dateDetailLabel: UILabel!
#IBOutlet weak var descriptionDetailLabel: UILabel!
var titleToPass: String!
var qtyToPass: String!
var descriptionToPass: String!
var dateToPass: String!
override func viewDidLoad() {
super.viewDidLoad()
titleDetailLabel.text = titleToPass
qtyDetailLabel.text = qtyToPass
dateDetailLabel.text = dateToPass
descriptionDetailLabel.text = descriptionToPass
}
// Do any additional setup after loading the view.
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
ItemTableViewCell
#IBOutlet weak var itemTitle: UILabel!
#IBOutlet weak var itemSubtitle: UILabel!
#IBOutlet weak var itemSubDetail: UILabel!
#IBOutlet weak var dateDetail: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
It is definitely best to avoid the force unwrap ! as much as possible, so I'd definitely recommend using guard let or if let in most cases. In addition to saving you from crashes, they also offer some great ways to see exactly where your code is failing that are otherwise tougher to track down.
For example, you have this line, which returns an optional NSIndexPath
let indexPath = self.tblItems.indexPathForCell(cell)
Rather than force unwrapping this optional, you can modify the line slightly to make it handle nil, and also add a print to tell you that this line failed, like this:
guard let indexPath = self.tblItems.indexPathForCell(cell) else {
print("No index path returned.")
return
}
If the value is not nil you can operate with the indexPath constant like you normally would. You'll probably want to continue this approach with most of these optionals, unless you're 100% sure they won't be nil in any situation.
As for your particular crash, you can correct me if I'm wrong, but I suspect it's because you have those four variables on your detailViewController that are being accessed in viewDidLoad, but you're only setting one of them in the segue. This means that in viewDidLoad you will be trying to access those (and they have the ! too), and since it won't find anything it'll crash immediately. See if fixing that, and getting rid of some of the !s helps.
Related
Problem I've found some questions asking how to disable a particular cell button in a table view, but what I want to do is to disable all instances of a button within a table view cell when another button is pressed.
Details I have a table view which is displaying a list of exercises and number of reps in a custom cell. Within the custom cell is also a button "swap" which allows a user to swap an exercise for another one before the workout starts.
When the user hits "start workout" (which triggers a timer) I want to disable all instances of the swap button (grey them all out and make non clickable).
Code
My workout cell class is here :
class WorkoutCell : UITableViewCell {
var delegate: WorkoutCellDelegate?
#IBAction func swapButtonPressed(_ sender: Any) {
delegate?.swapButtonTapped(cell: self)
}
#IBOutlet weak var exerciseName: UILabel!
#IBOutlet weak var repsNumber: UILabel!
}
protocol WorkoutCellDelegate {
func swapButtonTapped(cell: WorkoutCell)
}
What have I tried
The way I thought to do this was to add an IBOutlet (e.g. 'swapButton') for the button and then simply do something like :
#IBAction func startButtonPressed(_ sender: UIButton) {
WorkoutCell.swapButton.isenabled = false
}
But Xcode doesn't allow you to add IBOutlets to repeating cells so I'm a bit stuck.
I'm fairly new to delegates (managed to get it working for displaying the table view) so if it has something simple to do with that sorry!
Add a property to your viewcontroller:
var swapButtonsDisabled : Bool = false
In your cellForRow do something like this:
cell.swapButton.isEnabled = !self.swapButtonsDisabled
When the start button is pressed, set swapButtonDisabled to true and reload the tableView.
1- As you connect
#IBOutlet weak var exerciseName: UILabel!
create outlet for every btn
#IBOutlet weak var btn1: UIButton!
2- Add a property to the model array in the VC to hold the state of every cell
3- When you click the main btn fire the delegate method with the btn's cell
4- In VC delegate handle method disable the btns and change the state of the array index path
5- Don't forget to check state in cellForRow
You are pretty close. First I suggest you to be more specific and have the data you need in cell and use access control:
class WorkoutCell : UITableViewCell {
var workoutSwappable: (workout: Workout, canBeSwapped: Bool)? {
didSet {
swapButton.isEnabled = workoutSwappable?.canBeSwapped == true
// TODO: do additional setup here
}
}
weak var delegate: WorkoutCellDelegate? // Needs to be weak or you will have memory leaks
#IBAction private func swapButtonPressed(_ sender: Any) {
if let workoutSwappable = workoutSwappable, workoutSwappable.canBeSwapped == true {
delegate?.workoutCell(self, didTapWorkoutSwap: workoutSwappable.workout)
}
}
#IBOutlet private var exerciseName: UILabel!
#IBOutlet private var repsNumber: UILabel!
#IBOutlet private var swapButton: UIButton!
}
Ok so now in cell for row at index path all you need is something like:
cell.workoutSwappable = (self.items[0], self.canSwap)
On delegate you now have:
func workoutCell(_ sender: WorkoutCell, didTapWorkoutSwap workout: workout) {
self.currentWorkout = workout
self.canSwap = false
self.initializeTimer()
tableView.reloadData() // This will now flush all the buttons
}
I've a tableView with a cell for each pokemon in the "pokedex". When I press a button in the Cell, I want to show a view with details about this creature.
What I do is that I have a global variable "currentPokemon", that I set to the requested pokemon when the button is pressed. Here's my code for the Cell :
class PokemonTableViewCell: UITableViewCell {
var pokemon: Pokemon!
#IBOutlet weak var pokemonImage: UIImageView!
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var releaseDateLabel: UILabel!
#IBAction func setPokemon(sender: UIButton) {
currentPokemon = self.pokemon
}
}
When I try to access the currentPokemon var in the details view, I get a fatal error because currentPokemon is nil. How could I get this code to be executed before the segue ?
You need to add store for selected Pokemon with singleton instance like:
struct Pokemon {
let name: String
}
class PokemonStore {
static let instance = PokemonStore()
var currectPokemon: Pokemon?
}
Later you can get saved pokemon in any place of your code with PokemonStore.instance.currectPokemon
If you are using segue, you should pass data to detail controller in this UIViewController method:
func performSegue(withIdentifier identifier: String, sender: Any?)
As you can appreciate in this pic we have an App with three different VC's and a Last one with some variable data depending of the options selected on the previous ones.
So, for instance, in this case the user had selected a blue color, a suited style and a L as size.
Our idea is to pass data from the first VC, second VC, and the third VC to the gaps in Last VC.
Any suggestions? It would be very appreciated.
Create a model class where you can store those properties in :
class MyChoices {
var color : String? // or you could use enums for each of them
var style : String? // that would be a better choice, but for the
var size : String? // sake of simplicity I use strings in this example
}
then you pass a variable of type MyChoices from one VC to another in your prepareForSegue method
EDIT (some more info, see answer from FactorJose)
In VC 1 add your variable
class VC1: UIViewController {
#IBOutlet weak var nextOutlet: UIButton!
#IBOutlet weak var colourLabel: UILabel!
var choice : MyChoice?
...
and then further on :
#IBAction func redButton(sender: AnyObject) {
nextOutlet.hidden = false
colourLabel.text = "Red colour selected"
choice.color = "Red"
}
for all those IBActions.
then in your prepareForSegue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let nextVC = segue.destinationViewController as! VC2
nextVC.choice = self.choice
}
VC2 and VC3 are very similar again
Hi all! As you can appreciate in this pic we have an App with three different VC's and a Last one with some variable data depending of the options selected on the previous ones.
So, for instance, in this case the user had selected a blue color, a suited style and a L as size.
Our idea is to pass data from the first VC, second VC, and the third VC to the gaps in Last VC.
Any suggestions guys? It would be very appreciated
Code :
class VC1: UIViewController {
#IBOutlet weak var nextOutlet: UIButton!
#IBOutlet weak var colourLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
nextOutlet.hidden = true
}
#IBAction func redButton(sender: AnyObject) {
nextOutlet.hidden = false
colourLabel.text = "Red colour selected"
}
#IBAction func blueButton(sender: AnyObject) {
nextOutlet.hidden = false
colourLabel.text = "Blue colour selected"
}
#IBAction func greenButton(sender: AnyObject) {
nextOutlet.hidden = false
colourLabel.text = "Green colour selected"
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {}
}
VC2 and VC3 are the same as VC1 (same outlets and buttons)
lastVC
#IBOutlet weak var colourLabel: UILabel!
#IBOutlet weak var styleLabel: UILabel!
#IBOutlet weak var sizeLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
And the last class to store the strings
import UIKit
class MyChoices {
var colour : String?
var style : String?
var size : String?
}
What can we do?
An alternative to Glenn's answer is to use NSUserDefaults. Think of it like a mini key-value database for PropertyLists.
You can add a new key-value to it with
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(clothingColor, forKey: "Clothing Color")
And then retrieve a previously saved key-value by doing
let defaults = NSUserDefaults.standardUserDefaults()
let clothingColor = defaults.objectForKey("Clothing Color")
Since the user defaults is shared and stored in disk, the information will persist across your different view controllers. It also respects encapsulation, which is good object orientation practice, since your size controller won't have to know anything about color or style and vice versa.
I am using a master-detail model in Swift.
However, I want to send a class object created in detail view back to master view. I wrote a unwind function in the master view, but I cannot see the back button in the detail view so I cannot ctrl+drag it to the exit.
Does anyone know how to set the back button to make it visible?
Rather than worrying about hooking up something to the back button, you can update the model directly as the user updates the fields in the detail view controller. To do this you can pass a reference to some model object that contains the properties to be updated (make sure that's a reference type, e.g., a class, and not a struct, though).
For example:
class Person {
var firstName: String?
var lastName: String?
}
class MasterViewController: UIViewController {
#IBOutlet weak var firstNameLabel: UILabel!
#IBOutlet weak var lastNameLabel: UILabel!
var person = Person()
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let destination = segue.destinationViewController as? DetailViewController {
destination.person = person
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
firstNameLabel.text = person.firstName
lastNameLabel.text = person.lastName
}
}
class DetailViewController: UIViewController,UITextFieldDelegate {
var person: Person?
#IBOutlet weak var firstNameTextField: UITextField!
#IBOutlet weak var lastNameTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
firstNameTextField.text = person?.firstName
lastNameTextField.text = person?.lastName
}
// Note, I specified the detail view controller to be the delegate
// for the two text fields in IB: I then can detect when editing is
// done and act accordingly.
func textFieldDidEndEditing(textField: UITextField) {
switch textField {
case firstNameTextField:
person?.firstName = textField.text
case lastNameTextField:
person?.lastName = textField.text
default:
assert(false, "unidentified textField \(textField)")
}
}
}
You can have master view controller update itself in viewDidAppear, like I did above, or, better, you could add observers for the model properties. But hopefully it illustrates the basic idea.
I have a list of user profiles in a UICollectionView and when I tap on one of the users, I have a segue that Modally shows the users profile however I can't seem to pass the values from one View to another. Eg.. Image data, Username, etc.
On my UICollectionViewController I have the following:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showSelectedUserProfile" {
let cell = sender as! GrafterCell
let SP = segue.destinationViewController as! SelectedProfileViewController
let indexPath = self.collectionView?.indexPathForCell(cell)
var username = allUsernames[indexPath!.row]
SP.username.text = username
}
}
cell is the cell from the collection view, SP if the destination view controller
When I run the script I get the following error
fatal error: unexpectedly found nil while unwrapping an Optional value
it shows that SP.username is nil and I don't know why it is nil as it's connected to the Class and called username
class SelectedProfileViewController: UIViewController {
#IBOutlet weak var btnHideSelectedProfileViewController: UIButton!
#IBOutlet weak var userProfilePicture: UIImageView!
#IBOutlet weak var username: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
self.navigationController?.navigationBarHidden = true
}
#IBAction func btnHideSelectedProfileViewControllerClicked(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
}
You have an array of objects with a property username with a property text. From that I gather that you are perhaps storing an array of objects where the username property is a UILabel?
This would be very bad design. Your data array should just have data objects, not UI elements. Never use the content of UI elements to store and retrieve data (which is what you seem to be doing). UI elements should be used only to display data.
You need to give your destination controller a property of type String to hold the username information.
Also, don't call a label username which is misleading. If its a label, call it nameLabel to make the code readable and intelligible to an outside reader.