Swift: set variable before performing segue - ios

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?)

Related

Disable all UIButtons in UITableViewCell once a separate UIButton is pressed

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
}

Swift delegate function not being called from protocol

I am making a library of sorts. One screen, a table view controller, contains a list of books and the other screen, a view controller, allows you to add books.
I'm using the Add Book class to add books to the list of books screen. The add books screen contains text inputs to allow user to enter the book's name, author, publisher etc. When a user clicks a button after entering those fields, it creates a book object containing those attributes, and the user is directed back to the home screen showing the new book added to the list of books. The list of books is a table of rows. Each row contains one book respectively.
I'm using a protocol to call the delegate function in the list of books class, however it's not calling this function. The function not being called is newBook. My code seems fine to me, yet the newBook delegate function is not being called. Please help!. I've added relevant code below:
Add Book class:
import UIKit
protocol AddBookProtocol {
func newBook(book: Book)
}
class AddBookViewController: UIViewController {
var addBookDelegate: AddBookProtocol?
#IBOutlet weak var authorField: UITextField!
#IBOutlet weak var genreField: UITextField!
#IBOutlet weak var editionField: UITextField!
#IBOutlet weak var isbnField: UITextField!
#IBOutlet weak var nameField: UITextField!
#IBOutlet weak var dateField: UITextField!
#IBOutlet weak var publisherField: UITextField!
#IBOutlet weak var descriptionField: UITextField!
#IBAction func saveButton() {
print("HAHHHAHAHAH")
let name = nameField.text
let isbn = isbnField.text
let author = authorField.text
let publisher = publisherField.text
let publishDate = dateField.text
let genre = genreField.text
let edition = editionField.text
let desc = descriptionField.text
let book = Book(title: name!, isbn: isbn!, author: author!, publishDate: publishDate!, genre: genre!, publisher: publisher!, edition: edition!, desc: desc!)
addBookDelegate?.newBook(book: book)
navigationController?.popViewController(animated: true)
}
//rest of the code
Class displaying list of current books:
class CurrentBooksTableViewController: UITableViewController, AddBookProtocol {
var BooksList: [Book] = []
func newBook(book: Book) {
BooksList.append(book)
tableView.beginUpdates()
tableView.insertRows(at: [IndexPath(row: BooksList.count - 1, section: 0)], with: .automatic)
tableView.endUpdates()
tableView.reloadSections([SECTION_COUNT], with: .automatic)
}
//rest of the code
}
newBook is not being called for some reason.
Have you set the value of addBookDelegate? You can do this in the prepare for segue function in your table view controller.
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let addBookVC = segue.destination as? AddBookViewController {
addBookVC.addBookDelegate = self
}
}
You need to set the addBookDelegate for AddBookViewController as CurrentBooksTableViewController
Inside CurrentBooksTableViewController before presenting AddBookViewController you need to set addBookDelegate to self.
INSTANCE_OF_ AddBookViewController.addBookDelegate = self
Then only you can access the addBookDelegate functions from other side. otherwise if you check addBookDelegate will be = nil

Swift 3 pass values to Controller on UICollectionViewCell button click

I am beginner in iOS development and I have implemented following screen using UICollectionView :
CollectionViewCell File Code is:
import UIKit
class EventCell: UICollectionViewCell{
var classEvent: Event?
#IBOutlet weak var eventTitle: UILabel!
#IBOutlet weak var eventTeams: UILabel!
#IBOutlet weak var eventTime: UILabel!
#IBOutlet weak var eventTeamOneImage: UIImageView!
#IBOutlet weak var eventTeamTwoImage: UIImageView!
#IBOutlet weak var leaderboardButton: UIButton!
var datasourceItem: Any?{
didSet{
guard let event = datasourceItem as? Event else { return }
classEvent = event
eventTitle.text = "Match \(event.matchNo) (\(event.matchStage))"
eventTeams.text = "\(event.teamOne.nameAttr) vs \(event.teamTwo.nameAttr)"
eventTime.text = "\(event.getEventLockTimeAsString())"
eventTeamOneImage.loadImageUsingCache(withUrl: event.teamOne.flagPhoto)
eventTeamTwoImage.loadImageUsingCache(withUrl: event.teamTwo.flagPhoto)
leaderboardButton.addTarget(self, action: #selector(handleLeaderBoardClick), for: .touchUpInside)
}
}
#IBAction func leagueButton(_ sender: Any) {
}
weak var delegate: HomeControllerDelegate?
func handleLeaderBoardClick() {
if let matchId = classEvent?.id {
print(matchId)
delegate?.clickOnLeaderBoard(matchId: matchId)
}
}
}
Now on click on Leaderboard button(icon with 1,2,3) I would like to open new LeaderBoard Controller and pass matchId which is classEvent.id
How can I pass values to the new controller? And what is the best way to do that.
You can pass the match Id via segue:
In LeaderBoard Controller set a property:
var matchId:Int?
Set a segue between the controller and add an identifier:
On Click leaderboard button:
self.performSegueWithIdentifier("yourSegueIdentifier", sender: self)
Add the segue method:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue,identifier == "yourSegueIdentifier" {
let destinationVC = segue.destinationViewController as LeaderbardController
destinationVC.matchId = classEvent.id
}
}
}
Three easy steps to get what u want:
Make a BaseViewController class a subclass of UiViewController. This class would be the alternate of UiViewcontroller in your project,it means while creating any viewcontroller BaseViewController will be the parent class.
Declare a variable in BaseViewController.e.g- var data: Any?
Then while moving from a viewcontroller to another , simply assign any type of data to that variable declared in BaseViewController.
And in any lifecycle method of your new viewcontroller you will get that data using self.data.

Sending value back from detail to master

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.

How do I access text in UITextField from another ViewController (Swift)?

I have a UITextField on one ViewController and I want to be able to have a Label on the second ViewController = whatever the user enters.
However, I get an error when trying to do this. I know Swift doesn't use import so I'm not sure what to do.
// First View Controller
#IBOutlet weak var textOne: UITextField!
// Second View Controller
#IBOutlet weak var theResult: UILabel!
theResult.text = textOne.text
Error: Unresolved Identifier
It looks like that you in reality want to just access the text from the UITextField and not the field itself. So you should send the text to the second ViewController from your first one by using prepareForSegue.
But before, you have to set the name of your segue so that Swift knows which data to send:
As you see, we name the segue segueTest.
So now we can implement the prepareForSegue-method in the FirstViewController and set the data which should be sent to the second.
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "segueTest") {
var svc = segue!.destinationViewController as secondViewController;
svc.theText = yourTextField.text
}
}
That you can do that, you have to set a variable in your SecondViewController of course:
var theText:String!
You need to pass a reference to your FirstViewController instance to the SecondViewController instance at prepearForSegue...
Then do something like this
//FirstViewController
#IBOutlet weak var textOne: UITextField!
func getTextOne () -> String? {
return textOne.text
}
//SecondViewController
#IBOutlet weak var theResult: UILabel!
var firstViewControllerInstance: FirstViewController?
func showResult () {
theResult.text = firstViewControllerInstance?.getTextOne()
}
And you are done

Resources