UISegmentedControl reload certain page - ios

I'm using a third party library for my UISegmentedControl. The pages are initialised as following:
func carbonTabSwipeNavigation(carbonTabSwipeNavigation: CarbonTabSwipeNavigation, viewControllerAtIndex index: UInt) -> UIViewController {
switch index {
case 0:
return self.storyboard!.instantiateViewControllerWithIdentifier("FolderOverviewController") as! FolderOverviewController
case 1:
return self.storyboard!.instantiateViewControllerWithIdentifier("TopFoldersTab") as! TopFoldersTab
case 2:
return self.storyboard!.instantiateViewControllerWithIdentifier("CategoriesFolderTab") as! CategoriesFolderTab
default:
return self.storyboard!.instantiateViewControllerWithIdentifier("CategoriesFolderTab") as! CategoriesFolderTab
}
}
When I press the third segment, the user can go further down to see more details (via subviews on the same page). I would like the page to reload, every time I select the third segment again. (go back to the original CategoriesFolderTab page) . Currently I'm doing this with a ViewDidLoad(), but this is slowing down the app when you do it multiple times.
Is there a more correct way to do this? Thanks in advance

I think that calling viewDidLoad() is not the right approach for achieving this, instead, implement a new function that should contains code responsible for loading data in the UI components, for example:
override func viewDidLoad() {
super.viewDidLoad()
reloadUI()
}
func reloadUI() {
// filling UI components with desired data, such as:
// myLabel.text = "Hello World"
}
And somewhere in your code (where you want to reload), instead of calling viewDidLoad(), you should call reloadUI() method.
Hope this helped.

try this
Let folderOverVC = self.storyboard!.instantiateViewControllerWithIdentifier("FolderOverviewController") as! FolderOverviewController
Let topFoldersTab = self.storyboard!.instantiateViewControllerWithIdentifier("TopFoldersTab") as! TopFoldersTab
Let categoriesFolderTab = self.storyboard!.instantiateViewControllerWithIdentifier("CategoriesFolderTab") as! CategoriesFolderTab
func carbonTabSwipeNavigation(carbonTabSwipeNavigation: CarbonTabSwipeNavigation, viewControllerAtIndex index: UInt) -> UIViewController {
switch index {
case 0:
return folderOverVC
case 1:
return topFoldersTab
case 2:
return categoriesFolderTab
default:
return categoriesFolderTab
}
}
this initializes the view controllers once, so their respective viewDidLoad methods get called once initialized, not every time you tap a tab item

Related

Different functions for one bar button item

I have a view controller with a segmented control(only two segments: Songs and Playlists) and a bar button item. When the songs segment is selected, I want the bar button item to perform action1, and when the playlists segment is selected, I want the bar button item to perform action2.
In an attempt to do this I created this function which I declared in the viewDidLoad and in the viewDidAppear:
func settearButton() {
let indice = segmentFiltroMusica.selectedSegmentIndex
switch indice {
case 0: //Canciones
btnAgregarMusica.action = #selector(irAAgregarCancionesVC)
btnAgregarMusica.target = self
case 1: // Playlists
btnAgregarMusica.action = #selector(irAAgregarPlaylistsVC)
btnAgregarMusica.target = self
default: print("")
}
}
However, the bar button item is only performing action1, no matter what segment is selected. How could I fix this?
I don't think you actually want to change the action of the button. Handle the button tap, check the state of your segment control, then call the appropriate method.
func settearButton() {
let index = segmentFiltroMusica.selectedSegmentIndex
switch index {
case 0:
irAagregarCancionesVC()
case 1:
irAggregarPlaylistsVC()
default:
break
}
}
First you should put your function in the class and not the viewDidLoad() or viewDidAppear(). You can make it private since you seem to use it only localy.
If you are using storyboards with a ViewController you probably know that you can drag actions and outlets of your segment and button onto your viewController Class.
Here is how I would do this, by using a Boolean value and change this to select between my actions:
private var myChoice: Bool = false
#IBOutlet weak var segment: NSSegmentedControl!
#IBAction func segmentAction(_ sender: Any) {
let index: Int = self.segment.selectedSegment
switch index {
case 0:
myChoice = true
case 1:
myChoice = false
default:
fatalError("This segment does not exist!")
}
}
#IBAction func buttonAction(_ sender: Any) {
if myChoice {
// Do case one
} else {
// Do case two
}
}
If you have more elements in the segment to choose from, take an enum instead of the Boolean. I kept your switch which is good if you will add an enum later, else you can just replace the content of the segmentAction() with:
myChoice = self.segment.selectedSegment == 0

Table view continues to add rows while observing .childAdded even when child was not added in Firebase

I have a UITableView that gets populated by the following firebase database:
"games" : {
"user1" : {
"game1" : {
"currentGame" : "yes",
"gameType" : "Solo",
"opponent" : "Computer"
}
}
}
I load all the games in viewDidLoad, a user can create a new game in another UIViewController, once a user does that and navigates back to the UITableView I want to update the table with the new row. I am trying to do that with the following code:
var firstTimeLoad : Bool = true
override func viewDidLoad() {
super.viewDidLoad()
if let currentUserID = Auth.auth().currentUser?.uid {
let gamesRef =
Database.database().reference().child("games").child(currentUserID)
gamesRef.observe(.childAdded, with: { (snapshot) in
let game = snapshot
self.games.append(game)
self.tableView.reloadData()
})
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if firstTimeLoad {
firstTimeLoad = false
} else {
if let currentUserID = Auth.auth().currentUser?.uid {
let gamesRef = Database.database().reference().child("games").child(currentUserID)
gamesRef.observe(.childAdded, with: { (snapshot) in
self.games.append(snapshot)
self.tableView.reloadData()
})
}
}
}
Lets say there is one current game in the data base, when viewDidLoad is run the table displays correctly with one row. However anytime I navigate to another view and navigate back, viewDidAppear is run and for some reason a duplicate game seems to be appended to the games even though no child is added.
The cells are being populated by the games array:
internal func tableView(_ tableView: UITableView, cellForRowAt indexPath:
IndexPath) -> UITableViewCell {
let cell = Bundle.main.loadNibNamed("GameTableViewCell", owner:
self, options: nil)?.first as! GameTableViewCell
let game = games[indexPath.row]
if let gameDict = game.value as? NSDictionary {
cell.OpponentName.text = gameDict["opponent"] as? String
}
return cell
}
UPDATE:
Thanks to everyone for their answers! It seems like I misunderstood how firebase .childAdded was functioning and I appreciate all your answers trying to help me I think the easiest thing for my app would be to just pull all the data every time the view appears.
From what I can see, the problem here is that every time you push the view controller and go back to the previous one, it creates a new observer and you end up having many observers running at the same time, which is why your data appears to be duplicated.
What you need to do is inside your viewDidDisappear method, add a removeAllObservers to your gameRef like so :
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
guard let currentUserId = Auth.auth().currentUser?.uid else {
return
}
let gamesRef = Database.database().reference().child("games").child(currentUserId)
gamesRef.removeAllObservers()
}
I cannot see all your code here so I am not sure what is happening, but before adding your child added observer, you need to remove all the elements from your array like so :
games.removeAll()
Actually, as per best practices, you should not call your method inside your ViewDidLoad, but instead you should add your observer inside the viewWillAppear method.
I cannot test your code right now but hopefully it should work like that!
Let me know if it doesn't :)
UPDATE:
If you want to initially load all the data, and then pull only the new fresh data that is coming, you could use a combination of the observeSingleEvent(of: .value) and observe(.childAdded) observers like so :
var didFirstLoad = false
gamesRef.child(currentUserId).observe(.childAdded, with: { (snapshot) in
if didFirstLoad {
// add your object to the games array here
}
}
gamesRef.child(currentUserId).observeSingleEvent(of: .value, with: { (snapshot) in
// add the initial data to your games array here
didFirstLoad = true
}
By doing so, the first time it loads the data, .childAdded will not be called because at that time didFirstLoad will be set to false. It will be called only after .observeSingleEvent got called, which is, by its nature, called only once.
Try following code and no need to check for bool , Avoid using bool here its all async methods , it created me an issue in between of my chat app when its database grows
//Remove ref in didLoad
//Remove datasource and delegate from your storyboard and assign it in code so tableView donates for data till your array don't contain any data
//create a global ref
let gamesRef = Database.database().reference()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.games.removeAllObjects()
if let currentUserID = Auth.auth().currentUser?.uid {
gamesRef.child("games").child(currentUserID)observe(.childAdded, with: { (snapshot) in
self.games.append(snapshot)
self.tableView.dataSource = self
self.tableView.delegate = self
self.tableView.reloadData()
})
gamesRef.removeAllObserver() //will remove ref in disappear itself
//or you can use this linen DidDisappear as per requirement
}
else{
//Control if data not found
}
}
//TableView Delegate
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.games.count == 0{
let emptyLabel = UILabel(frame: CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: self.view.bounds.size.height))
emptyLabel.text = "No Data found yet"
emptyLabel.textAlignment = .center
self.tableView.backgroundView = emptyLabel
self.tableView.separatorStyle = .none
return 0
}
else{
return self.games.count
}
}
observe(.childAdded) is called at first once for each existing child, then one time for each child added.
Since i also encounter a similar issue, assuming you don't want to display duplicate objects, in my opinion the best approach, which is still not listed in the answers up above, is to add an unique id to every object in the database, then, for each object retrieved by the observe(.childAdded) method, check if the array which contains all objects already contains one with that same id. If it already exists in the array, no need to append it and reload the TableView. Of course observe(.childAdded) must also be moved from viewDidLoad() to viewWillAppear(), where it belongs, and the observer must be removed in viewDidDisappear. To check if the array already includes that particular object retrieved, after casting snapshot you can use method yourArray.contains(where: {($0.id == retrievedObject.id)}).

Select tab programmatically custom tab bar ios

I have used the following tutorial to implement custom tabs in ios
Its working fine when i tap on any of the item. Now I want to move it Programatically. e.g. I received the notification form Firebase and want to open third tab. But I am getting nil. I have referenced the MainTabbarController instance in appdelegate.
Here is what i have tried
In CustomTabBar
func customTapped(index :Int){
animateTabBarSelection(from: selectedTabBarItemIndex, to: index)
selectedTabBarItemIndex = index
delegate.didSelectViewController(self, atIndex: index)
}
In AppDelegate
mainTabBarController?.customTabBar?.customTapped( index: 2)
From the link that you have provided along with the question, the following are the statements that I found to be responsible for switching the tabs
func barItemTapped(sender : UIButton) {
let index = tabBarButtons.indexOf(sender)!
animateTabBarSelection(from: selectedTabBarItemIndex, to: index)
selectedTabBarItemIndex = index
delegate.didSelectViewController(self, atIndex: index)
}
You can just modify this code as
func barItemTapped(sender : UIButton) {
let index = tabBarButtons.indexOf(sender)!
tabSelectedAt(index)
}
func tabSelectedAt(_ index:Int) {
if selectedTabBarItemIndex != nil {
animateTabBarSelection(from: selectedTabBarItemIndex, to: index)
}
selectedTabBarItemIndex = index
delegate.didSelectViewController(self, atIndex: index)
}
So call the function func tabSelectedAt(_ index:Int) with the index of the tab which you wants to switch.

how can I refresh a single button in UITableViewCell instead of refreshing whole table or whole cell?

In my swift app I have a UITableView with one static cell and many dynamic cells.
Static cell contains different fields, such as labels, map (taken from MapKit) and a button, that indicates whether user voted up or not.
Now, when user presses the button, I want to change its color, possibly without refreshing anything else.
So far my code looks like this:
var currentUserVote:Int = 0
func tableView(_ tview: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (indexPath as NSIndexPath).row == 0 {
let cell = tview.dequeueReusableCell(withIdentifier: "cellStatic") as! VideoDetailsCell
fetchScore(cell.score)
let voteUpImage = UIImage(named: "voteUp");
let tintedVoteUpImage = voteUpImage?.withRenderingMode(UIImageRenderingMode.alwaysTemplate)
cell.voteUpButton.setImage(tintedVoteUpImage, for: UIControlState())
checkUsersVote() { responseObject in
if(responseObject == 1) {
cell.voteUpButton.tintColor = orangeColor
} else if (responseObject == -1){
cell.voteUpButton.tintColor = greyColor
} else {
cell.voteUpButton.tintColor = greyColor
}
self.currentUserVote = responseObject
}
//map handling:
let regionRadius: CLLocationDistance = 1000
let initialLocation = CLLocation(latitude: latitude, longitude: longitude)
centerMapOnLocation(initialLocation, map: cell.mapView, radius: regionRadius)
//cell.mapView.isScrollEnabled = false
cell.mapView.delegate = self
.
.
.
return cell
} else {
//handle dynamic cells
}
}
So in the method above I'm checking if user voted already and based on that I'm setting different color on the button. I'm also centering the map on a specific point.
Now, since it's a static cell, I connected IBAction outlet to that button:
#IBAction func voteUpButtonAction(_ sender: AnyObject) {
if(currentUserVote == 1) {
self.vote(0)
}else if (currentUserVote == -1){
self.vote(1)
} else {
self.vote(1)
}
}
and the vote method works as follows:
func vote(_ vote: Int){
let indexPath = IndexPath(row: 0, section: 0)
let cell = tview.dequeueReusableCell(withIdentifier: "cellStatic") as! VideoDetailsCell
switch(vote) {
case 1:
cell.voteUpButton.tintColor = orangeColor
case 0:
cell.voteUpButton.tintColor = greyColor
case -1:
cell.voteUpButton.tintColor = greyColor
default:
cell.voteUpButton.tintColor = greyColor
}
tview.beginUpdates()
tview.reloadRows(at: [indexPath], with: .automatic)
tview.endUpdates()
currentUserVote = vote
//sending vote to my backend
}
My problem is, that when user taps the button, he invokes the method vote, then - based on the vote, the button changes color, but immediately after that method cellForRow is called and it changes the color of the button again. Also, it refreshes the map that's inside of it.
What I want to achieve is that when user taps the button, it should immediately change its color and that's it. Map stays untouched and the button is not changed again from cellForRow.
Is there a way of refreshing only that particular button without calling again cellForRow?
First of all, you confuse static and dynamic cells. You can use static cells only in the UITableViewController and you can't use static and dynamic cell at the same time.
I strongly recommend you not to use cell for storing map and button. All elements from the cell will be released after scrolling it beyond the screen.
I can advise you use TableViewHeaderView for this task. In this case you will be able set button and map view as #IBOutlet.
(See more about adding tableview headerView. You can also set it from interface builder.)
Another way is change tableView.contentInset and set your view with map and button as subview to tableView. This method is used when you need create Stretchy Headers.
It should be quite easy, simply do it in your button handler. The sender argument there is the button object that caused the action. When you were connecting it from IB there was a dropdown to select sender type, you may have missed it and the whole thing would have been obvious with UIButton type there.
Simply change your handler like this :
#IBAction func voteUpButtonAction(_ sender: UIButton) {
if(currentUserVote == 1) {
self.vote(0)
}else if (currentUserVote == -1){
self.vote(1)
} else {
self.vote(1)
}
sender.backgroundColor = yourFavouriteColor
}
Another approach would be to create an IBOutlet for your button, since its from a static cell, and then you would be able to reference it from any place in your view controller.
In this call:
func tableView(_ tview: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
I see it calls checkUsersVote() which I'm guessing should get the updated value set in the vote() call. Could the problem be that you aren't doing this
currentUserVote = vote
until after reloadRows() is called?

swift alamofire show viewcontroller after remote data has loaded

I wonder how I can show a VC after the remote data has loaded. I am not using a tableView but a normal VC.
My code look like this:
viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
loadItemData(id)
}
func loadItemData(aId: Int) {
Service.getItem(aId) { (JSON) -> () in
self.iData = JSON
self.configureData(self.iData)
}
}
func configureData(iData: JSON) {
if let type = iData["item_type"].int {
if let == 1 {
someButton.hidden = true
}
}
if let title = iData["item_title"].string {
titleLabel.text = title
}
}
What happens is that my VC first loads with the button visible and with my text label containing "dummy text" from storyboard, then when the data has loaded the button will hide and the text label will change.
So my question now is how I can hide my VC or show some loading indicator until the data has loaded.
Also worth saying this is the 2nd view. My apps start with a tableView and when you click on a cell you end up in this VC. So I could also load the data when the cell gets clicked then pass it to this VC.
Using the activity indicator is better, here's how to do it
First make sure you add Activity Indicator in your VC
override func viewDidLoad() {
super.viewDidLoad()
self.myActivityIndicator.startAnimating()
loadItemData(id)
}
func loadItemData(aId: Int) {
Service.getItem(aId) { (JSON) -> () in
self.iData = JSON
self.configureData(self.iData)
self.myActivityIndicator.stopAnimating()
}
}
Rather than try to hide the VC (try to load the data and pass it), it will makes the apps feel not responsive, because you have to wait the data loaded and then the VC will show.

Resources