I'm currently working on an app in which I would like the users to be able to favourite a button and that button then gets added to the 'Favourites' section for that particular ViewController.
The app is a soundboard so it is split into (currently) 2 characters, within those characters I have categories of sound files for the users to choose from, once the user selects a category it will load up the ViewController with a set of sounds which can be played by pressing the button (very simple I know).
I'm trying to implement a 'Favourite' function in order to allow the user to favourite the buttons of their choice and in doing so, the new 'favourited' button will be displayed in a new ViewController.
I've browsed through here and found some stuff in regards to NSUserDefaults
I'm new to Swift and iOS development as a whole so if someone could be kind enough to guide me in the right direction I would be very grateful.
I know how to set the Image of the favourite button to change whether it has been set as a favourite or not, I also noticed that it suggests I place the favourites in a UITableViewController. My app is currently using a UITabBarController with 4 different ViewControllers off them.
If you would like screenshots of my Storyboard to get a better understanding please let me know and I shall update the post, also if any code is required please let me know and once again, I shall update the post!
EDIT: I will happily add a button which when pressed loads a table view if someone knows how to add a normal button into a table view so when pressed it shows the favourites
EDIT 2: Image
try to do as follows-
Take a UITabBarController having one viewController as soundsViewController and another tableViewController as favouritesTableViewController shown as below image-
Now add these sample code to soundsViewController-
var favListArray:NSMutableArray = []
#IBOutlet weak var tableView: UITableView!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if NSUserDefaults.standardUserDefaults().objectForKey("favList") != nil {
favListArray = NSMutableArray.init(array: NSUserDefaults.standardUserDefaults().objectForKey("favList") as! NSMutableArray)
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 10
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("soundCell", forIndexPath: indexPath) as! TableViewCell
cell.titleLbl.text = NSString.localizedStringWithFormat("Sound %d", indexPath.row) as String
if favListArray.containsObject(cell.titleLbl.text!) {
cell.addTOFVrtBtn.setTitle("-", forState: UIControlState.Normal)
}else{
cell.addTOFVrtBtn.setTitle("+", forState: UIControlState.Normal)
}
cell.addTOFVrtBtn.tag = indexPath.row
cell.addTOFVrtBtn.addTarget(self, action:#selector(addToFav) , forControlEvents: UIControlEvents.TouchUpInside)
return cell
}
func addToFav(sender:UIButton) {
let cell = self.tableView.cellForRowAtIndexPath(NSIndexPath.init(forRow: sender.tag, inSection: 0)) as! TableViewCell
if favListArray.containsObject(cell.titleLbl.text!) {
favListArray.removeObject(cell.titleLbl.text!)
}else{
favListArray.addObject(cell.titleLbl.text!)
}
tableView.reloadData()
NSUserDefaults.standardUserDefaults().setObject(favListArray, forKey: "favList")
}
these is a sample code, here TableViewCell is subclass of UITableViewCell having titleLbl and addTOFvrtBtn.
Add below sample code to favouritesTableViewController -
varfavSoundList:NSMutableArray = []
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if NSUserDefaults.standardUserDefaults().objectForKey("favList") != nil {
favSoundList = NSMutableArray.init(array: NSUserDefaults.standardUserDefaults().objectForKey("favList") as! NSMutableArray)
print(favSoundList)
self.tableView.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return favSoundList.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel?.text = favSoundList.objectAtIndex(indexPath.row) as? String
return cell
}
Here is the o/p screen demo-
I hope this will help you or you can get an idea how to make Favourites list and Favourites section..Thanks
Don't think of it as favouriting a button, a button is just how you're displaying the data in your app - what you're really favouriting is the data itself, so in this case a particular sound file.
So, your current data is a list of sound files, or the path to those files. Your favourites list can be exactly the same. I imagine you might be simply getting a list of files from disk to display so your list of favourites will need to be stored differently. That could be in user defaults, or in a plist file, either way it's an array of strings and both are simple options to store the array.
When storing you just set the array into defaults with a key, or write it to a file. When you read back you should either read from user defaults and then make a mutableCopy or use NSMutableArray to read the file in so you have an editable instance afterwards and you can add and remove favourites from the list.
Your approach of using buttons to represent the sound files is probably a lot more code and effort than you require. Table views are specifically designed to show a list of things, and there's no reason that a table view can't look like a list of buttons (while being much more flexible and much less code to create).
Related
I have a TableViewController (lets call TVC1) with a row that says "OD" (which stands for Outer Diameter).
Upon selecting this row, a bunch of rows in a new TableViewController (lets call TVC2) containing the various OD (casingOD in my code) shows. What I want to happen is when the user selects the OD it will segue back to the main TableViewController with the string that corresponds to the user selection. My code for this currently fails...Could anyone help point me in the right direction? If you require TVC1 code i'll happily post it, i'm just trying to save any unneccessary code reading for you folks :)
My TVC2 code is as follows:
import UIKit
class CasingSelectionTableViewController: UITableViewController {
var selectedData: Data?
let casingOD = ["114.3", "127.0", "139.7", "168.3" , "177.8", "193.7", "219.1", "244.5", "247.6", "273.1", "298.4", "298.4", "339.7", "406.4", "473.0", "508"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
switch selectedData! {
case .OuterDiameter:
print(casingOD)
case .Weight:
print(casingWeight114) // I deleted the casingWeight114 line of code as its not required for this question
case .InnerDiameter:
print(id114) // I deleted the id114 line as its not required for this question
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return casingOD.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var casingSpec: UITableViewCell!
if selectedData == Data.OuterDiameter {
casingSpec = tableView.dequeueReusableCellWithIdentifier("selectedCasingSpec", forIndexPath: indexPath)
let casingODSpec = casingOD[indexPath.row]
casingSpec.textLabel?.text = casingODSpec
return casingSpec
} else {
return casingSpec
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selection: UITableViewCell!
selection.textLabel?.text = indexPath.row as! String
}
What I want to happen is when the user selects the OD it will segue back to the main TableViewController with the string that corresponds to the user selection.
First of all you'll need to implement a way for TVC2 to notify TVC1 that a value has been selected.
A common way to do such thing is by using delegation. You can define a delegate protocol like this:
protocol TVC2Delegate {
func tvc2(tvc2: TVC2, didSelectOuterDiameter outerDiameter: String)
}
Then add a var delegate: TVC2Delegate? property to TVC2.
You'll then make TVC1 comform to TVC2Delegate by implementing that method in TVC1.
When presenting TVC2 from TVC1 remember to set it as the delegate for TVC2.
// In TVC1
tvc2.delegate = self
To connect TVC1 and TVC2 you could add a bit o logic to your tableView(tableView:,didSelectRowAtIndexPath:) method call the delegate with the selected value
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let stringValue = indexPath.row as! String
// Do anything you need to do related to TVC2 here.
// Then finally
delegate?.tvc2(self, didSelectOuterDiameter: stringValue)
}
Finally, in TVC1's implementation of the delegate method you can take care of dismissing TVC2 if needed.
Update:
This is how the final implementation of these bits might look like:
// In TVC1
class TVC1: UITableViewController, TVC2Delegate {
// ...
// Implement the method(s) of TVC2Delegate
func tvc2(tvc2: TVC2, didSelectOuterDiameter outerDiameter: String) {
// Do whatever you need to do with the outerDiameter parameter
}
}
// In TVC2
protocol TVC2Delegate {
func tvc2(tvc2: TVC2, didSelectOuterDiameter outerDiameter: String)
}
class CasingSelectionTableViewController: UITableViewController {
var delegate: TVC2Delegate?
// ...
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let stringValue = casingOD[indexPath.row]
// Do anything you need to do related to TVC2 here.
// Then finally
delegate?.tvc2(self, didSelectOuterDiameter: stringValue)
}
}
Use the delegate approach as suggested in the answer by #Mokagio. And in case you're having issue in getting the string, here is the answer
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! UITableViewCell
let stringValue = cell.textLabel.text //You can get this from your datasource as well)
//call the delegate
}
I am working on an app that needs to display five different images inside of five different tableview cells. This is the storyboard that I have for the cell.
This is the code I have inside of the tableviewcontroller.
class MasterViewController: UITableViewController, UITableViewDelegate, UITableViewDataSource {
var returnTable = 1
var icons: [String] = ["settings", "calendar", "classes", "envelope", "grades"]
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 0
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return icons.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! HomeScreenCells
var image : UIImage = UIImage(named: icons[indexPath.row])!
cell.icon.image = image
return cell
}
I also have a custom class for the tableviewcells. Also, I have the correct reuse identifier for the cell. Still, the image does not appear. Not only does the image not appear, nothing appears. When I change the background, the background does not change in the simulator. This is all I get in the simulator.
I have the classes linked up correctly for the split view controller. I have no idea why nothing is appearing in the table. Any help would be appreciated.
I think your problem may be that you have the number of sections set to 0. Either set it to 1, or leave out the block of code completely.
As stated above, return 1 in number of sections (or the number of cells you need). And if your custom cell class has a .xib, you also need to register it, sometimes after you init the tableview
tableView.registerNib( UINib(nibName: "HomeScreenCells", bundle: nil), forCellReuseIdentifier: "Cell")
I'm working on an iPhone app that should display images in a UICollectionView. The images are saved on the Parse cloud, and I'm using a subclass of PFQueryCollectionViewController to make the query and display the images.
Tapping on a MKMapView callout triggers the segue that shows the CollectionViewController. The images do not appear in the cells the first time the CollectionViewController is presented. However, if I go back to the MapView, and then return to the CollectionViewController, the images appear in the cells.
How can I get the images to appear the first time the PFQueryCollectionViewController is presented?
Here's my code:
class PhotoGridCollectionViewController: PFQueryCollectionViewController {
override func viewDidAppear(animated: Bool) {
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: UICollectionViewDataSource
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
//#warning Incomplete method implementation -- Return the number of sections
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//#warning Incomplete method implementation -- Return the number of items in the section
return self.objects.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFCollectionViewCell? {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("venuePhotoThumb", forIndexPath: indexPath) as! PhotoGridCollectionViewCell
if let pfObject = object {
cell.imageView.file = pfObject["imageFile"] as? PFFile
cell.imageView.loadInBackground({ (img, err) -> Void in
println("Download complete")
})
}
return cell
}
}
Since I'm using Storyboards, I pass my custom class the parseClass by setting it in the Attribute Inspector:
As you can see, I'm using the loadInBackground method of the PFImageView to load the images asynchronously, and I think this the problem might be caused by the images being downloaded after the cell is returned, but I don't have any other ideas. Does anyone know why the images are not appearing the first time the view is presented?
My co-developer finally found the answer to this. We had to set a placeholder image for the PFImageViews when configuring the cells in cellForItemAtIndexPath. The code examples on Parse do include setting the placeholder image, but they don't say that it is required for the PFQueryCollectionViewController to work properly. We considered it an aesthetic touch that we could come back to later.
So the answer is: in order for images to load properly in the cells of a PFQueryCollectionViewController you MUST provide a placeholder image when configuring the cells in cellForItemAtIndexPath.
Here's the code that worked:
let placeholder = UIImage(named: "myPlaceholderImage") //an image from images.xcassets in your xcode project
cell.imageView.image = placeholder
if let pfObject = object {
cell.imageView.file = pfObject["image"] as? PFFile
cell.imageView.loadInBackground()
}
I am writing a note taking app, just for reference. I have arrays set up, and a table that feeds off the arrays with the following code:
import UIKit
import Foundation
var tableData = ["Pancake Recipe", "Costume Party", "Camping Supplies"]
var tableSubtitle = ["Some Milk and some Flour", "Let's dress up like Jen", "Tenting with Lucy"]
class ViewController: UIViewController {
func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
return tableData.count
}
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier:"cell")
cell.textLabel!.text = tableData.reverse()[indexPath.row]
cell.detailTextLabel!.text = tableSubtitle.reverse()[indexPath.row]
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
var listTitle = "Notes"
self.title = listTitle
UIApplication.sharedApplication().statusBarStyle = .LightContent
}
override func viewDidAppear(animated: Bool) {
println(tableSubtitle)
}
}
A user creates a new title and subtitle for the cell on a different page, and these are added to the arrays (the tableData and tableSubtitle arrays). I know the adding of the data works correctly, because when I watch the console it prints both the updated arrays perfectly.
When I then return to the main view controller, I am presented with an extra cell (as I wanted) but instead of the new content that I want, it is instead just a duplicate of the 'Pancake Recipe' cell.
Do I need to refresh the content of the cells when the view loads again? If so, how can I do this?
Thanks :)
For reference, here is a picture of the Table View after data has been added to both the arrays twice, and I have then returned to the Table View, despite the fact both the arrays now contain two extra and distinct entries (checked using println(tableData) and println(tableSubtitle)
The provided code does not provide much information to find the issue, probably the issue will be with data adding code.
For refreshing the table view use:
override func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated)
self.yourTableView.reloadData()
}
I have created a segue from a button, but it has given me the following error. I am using Swift and Xcode 6.
/Users/dave_cooljamdesign/Desktop/XcodeDev/findaphysio2/FindaPhysio/FindaPhysio/Base.lproj/Main.storyboard: Segues initiated directly from view controllers must have an identifier
How do I give them an identifier when I'm draging from table view to the detail view?
Below is some code to show what I am doing in the view controller
import UIKit
import MapKit
class ClinicsTableViewController:
UITableViewController {
var clinic = [Clinics]()
#IBOutlet var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
self.clinic = [Clinics(name:"Chocolate")]
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 0
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return self.clinic.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//ask for a reusable cell from the tableview, the tableview will create a new one if it doesn't have any
let cell = self.tableView.dequeueReusableCellWithIdentifier("findaphysioCell", forIndexPath: indexPath) as UITableViewCell
// Get the corresponding candy from our candies array
let _clinic = self.clinic[indexPath.row]
// Configure the cell
cell.textLabel!.text = _clinic.name
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
return cell
}
}
Find the segue on the storyboard (the line between view controllers with a circle in the middle) and click it.
Then open the attributes inspector in the utilities pane and give it an identifier.
As gabbler mentioned, the shortcut command + option + 4 will immediately open the attributes inspector.
In your storyboard, select the segue between the two view controllers. Go to the attributes inspector on the right and add an identifier name.
In your code do something like this:
self.performSegueWithIdentifier("setupView", sender: self)
Obviously change 'setupView' with whatever you set for the segue identifier.