I have tableview that I add cells to via an "add button" and a second view controllers. I currently have the following code setup for my tableview and it sorts the cells alphabetically whenever the app loads. My issue is that when I add a new recipe it does not re-sort automatically. What is the best way to do this? Should my reloadData() be somewhere else? Or perhaps put it in the Segue from view controller back to table view?
override func viewDidLoad() {
configureView()
if let savedRecipes = loadRecipes() {
recipes2 += savedRecipes
recipes2 = recipes2.sort({current, next in return current.name < next.name})
recipeList.reloadData()
}
}
You could add reloadData() to the viewWillAppear() or viewDidAppear() methods in your table view controller. That way, whenever you return to the view, it should be showing the most up to date data. For example:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
reloadData()
}
If the recipe is a class on its own you can do it like this:
if let savedRecipes = loadRecipes() {
recipes2 += savedRecipes
recipes2.sortUsingComparator {
let r1 = $0 as! Recipe
let r2 = $1 as! Recipe
if r1.title < r2.title {
return .OrderedAscending
}
if r1.title == r2.title {
return .OrderedSame
}
return .OrderedDescending
}
self.tableView.reloadData()
}
This will sort your recipes in ascending order.
You can use the ModelAssistant framework to show recipe objects in tableview. This is a library to mediate between tableView and model. you can set your sort method at the begin of viewcontroller then each new recipe which you add to your model, the model automatically will be sort and also your tableview will be updated too.
Related
I have got a simple view in swift and put 9 buttons as a 3x3 grid onto it, now i need the tag for each button and don't know how to get each button so I can get the tag property using a for loop. Does anyone know how i can get the button? Is there a function to get a view at a specified location?
Frankenstein's answer with .subviews works fine, but you can even do it more swifter using the built-in function viewWithTag(_:):
override func viewDidLoad() {
super.viewDidLoad()
// ...
// targeted view must have been added to the subview by now
if let taggedView = view.viewWithTag(1) {
print("Got the view!")
}
// you can even try to cast directly
if let taggedButton = view.viewWithTag(1) as? UIButton {
print("Got the button!")
}
// if you insist on using a for loop, you could use it like this
let highestTag = 10
for i in 0...highestTag {
if let taggedButton = view.viewWithTag(i) as? UIButton {
// here you go
}
}
}
For both approaches keep in mind, that the view you're looking for has already been added as a subview beforehand, otherwise you won't be able to search for it
You can use the subviews property of UIView and loop through each subview to check for tag you need:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
for subview in view.subviews {
if subview.tag == 1, let button = subview as? UIButton {
print("Got the button I need.")
}
}
if let button = view.viewWithTag(1) as? UIButton {
print("Got the button I need.")
}
}
}
Update: You can also use viewWithTag to find you view with a particular tag from within your subviews.
I am making an app where I take in user input and display it as a chart, which requires an array of data. I have managed to save data in an array using core data and I cannot figure out how to share that data from one tab to the other TabViewController.
here is how the data is stored and fetched in the FirstViewController
let number = Numbers(context: PersistenceService.context)
number.numberInArray = Int16(numberEnteredInSlider)
PersistenceService.saveContext()
testArray.append(Int(Double(number.numberInArray)))
var numbers = [Numbers]() // Where Numbers = NSManaged Class
var fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Numbers")
do {try numbers = PersistenceService.context.fetch(fetchRequest) as! [Numbers]
for number in numbers {
print(number.numberInArray)
}
}catch {
print("error")
}
and here is the output(printed testarray):
SAVED
2
5
6
5
Now I want to share this test array from one view controller to another(chartsViewController)
this is what I have tried
class chartsViewController: UIViewController {
let mainVC = mainViewController(nibName: "mainViewController", bundle: nil)
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print(mainVC.testArray)
updateGraph()
func updateGraph() {
var lineChartEntry = [ChartDataEntry]() //this is the Array that will eventually be displayed on the graph.
for i in 0..<mainVC.testArray.count {
//
let value = ChartDataEntry(x: Double(i), y: Double(mainVC.testArray[i]))
// here we set the X and Y status in a data chart entry
lineChartEntry.append(value)
// here we add it to the data set
}}
//only showing the part needed. I have tried the same solution with another array and it worked.
}
and the output comes as [0]
I have also tried making a singleton but that didn't work out.
To pass data between tabs on UITabBarController /tabBar, what needs to be done is to have an intermediate. (This is usually the main UITabBarController)
Pic of UITabBarController and the child tabbar
Create a Class and link it to this TabBarController within IB
class BaseTBController: UITabBarController {
// Provide the variable which we want to pass
var workoutTitle: String = "Select a Workout"
override func viewDidLoad() {
super.viewDidLoad()
}
}
Assuming you want to pass data from TabBar2 to TabBar1, then on TabBar2 (in this case, I have it as a UITableView). In the delegate method:
extension VCLibrary: UITableViewDelegate{
// method to run when table view cell is tapped
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// prepare to store the data to be passed to another TabBar
let tabbar = tabBarController as! BaseTBController
tabbar.workoutTitle = jsonErgWorkouts[indexPath.row].title
// Automatically select Tab1 after choosing
self.tabBarController?.selectedIndex = 0
// Deselect the selected row once we move to Tab1
tableView.deselectRow(at: indexPath, animated: true)
}
}
After selecting the data to be passed, the code (above) will automatically switch to Tab1. Within Tab1, the following code is aimed to receive the passed data
override func viewDidAppear(_ animated: Bool) {
// Obtain Passed in values from BaseTBController
let tabbar = tabBarController as! BaseTBController
// populate the Title as passed from Tab2
workoutTitleLabel.text = tabbar.workoutTitle
}
I learned this from:
https://www.youtube.com/watch?v=GL8-eM93EvQ
My code below removes a label from a uicollectionview. However the labal is just remove and I would like the whole cell to be removed. My code below is the button controlling these actions. I am using core data on the label. I also added a before and after pitcure of the desired result.
var itemName : [Item] = []
#objc func elete(_ sender:UIButton){
let itemName1 = itemName[sender.tag]
itemName1.atBATS?.removeAll()
itemName1.image?.removeAll()
collectionView.reloadData()
}
}
You need to delete the item from the dataSource/core data and then reload the collection view.
#objc func elete(_ sender:UIButton){
itemName.remove(at: sender.tag) // this removes from array, but you should also remove from core data.
collectionView.reloadData()
}
I have a home UIViewController that contains a UITableView. On this view controller I display all games for the current user by reading the data from firebase in ViewWillAppear. From this view controller a user can press a button to start a new game and this button takes them to the next view controller to select settings, this then updates the data in firebase and adds a new child. Once they navigate back to the home view controller is there anyway to just update the data with the new child added instead of loading all the games for the table view again as I am currently doing?
This is my current code:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let currentUserID = Auth.auth().currentUser?.uid {
let gamesRef = Database.database().reference().child("games").child(currentUserID)
self.games = []
gamesRef.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let game = child as! DataSnapshot
self.games.append(game)
self.tableView.reloadData()
}
})
}
}
I think you can use observeSingleEvent and .childAdded
You can do the loading of all the data in viewDidLoad and of single child in viewWillAppear since viewDidLoad will be called once initially
Since both methods will be called initially, so we can have a bool flag so we can control which code runs initially and which does not , since viewWillAppear is called after viewDidLoad so we change the value of this flag in viewWillAppear method and then control the execution of code inside viewWillAppear using this flag
class SomeVC: UIViewController {
var flag = false
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if flag {
//do your work here
}else {
flag = true
}
}
}
Edited:
Another solution can be that you dont do anything in viewDidLoad and do the work only in viewWillAppear since in this particular scenario data in both calls are related (fetching the data from Firebase)
class SomeVC: UIViewController {
var flag = false
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if flag {
//fetch only one child
}else {
//fetch all the data initially
flag = true
}
}
}
I have dynamic tableview, wherein one of the cell (duration) when tapped opens another view controller which is a list of duration viz (30 min, 1 hour, 2 hours and so fort). One of the durations when selected should display the selected duration in the first view controller. I am able to pass the data back to first view controller using unwind segue but unable to display the passed value. DOn't know whats missing.
I am displaying the code below:
FIRST VIEW CONTROLLER (CALLING)
#IBAction func unwindWithSelectedDuration(segue:UIStoryboardSegue) {
var cell = tableView.dequeueReusableCellWithIdentifier("durationCell") as! durationTableViewCell
if let durationTableViewController = segue.sourceViewController as? DurationTableViewController,
selectedDuration = durationTableViewController.selectedDuration {
cell.meetingDurationCell.text = selectedDuration
duration = selectedDuration
}
SECOND VIEW CONTROLLER (CALLED)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SaveSelectedDuration" {
if let cell = sender as? UITableViewCell {
let indexPath = tableView.indexPathForCell(cell)
if let index = indexPath?.row {
selectedDuration = durationList[index]
}
}
}
}
tableView.dequeueReusableCellWithIdentifier should only be called within tableView:cellForRowAtIndexPath:. It has no use outside this context.
The easiest fix is to just reload the table once you have stored the selected duration:
#IBAction func unwindWithSelectedDuration(segue:UIStoryboardSegue) {
if let durationTableViewController = segue.sourceViewController as? DurationTableViewController {
selectedDuration = durationTableViewController.selectedDuration
tableView.reloadData()
}
}
Note that this assumes you only need one selectedDuration for your whole table, rather than one per row. If you need one per row, I assume you have them stored in an array somewhere, so it is that array that should be updated instead before the reloadData.