I currently try to find a way to remove while run the app a TabBar Item, i found a way to enable or disable it but not to complete remove it.
For disable it i do:
In ViewDidLoad
if let tabBarItem = self.tabBarController?.tabBar.items?[3] as? UITabBarItem {
tabBarItem.enabled = false
}
This works well but still the user can see the TabBar item and i ll simply complete remove it, is there a way?
I want to trigger the TabBarItem via Parse, if i set the Parse Data to true it should show other way it should not.
You want to set the viewControllers property of your tabBarController with an array where you excluded the particular viewController that you don't want to have anymore.
if let tabBarController = self.tabBarController {
let indexToRemove = 3
if indexToRemove < tabBarController.viewControllers?.count {
var viewControllers = tabBarController.viewControllers
viewControllers?.remove(at: indexToRemove)
tabBarController.viewControllers = viewControllers
}
}
For those who just want to disable one item. Use this code from #Daniele's solution. and place it in your UITabBarController class
viewDidLoad() {
let index = 0 //0 to 5
viewControllers?.remove(at: index)
}
Swift 5: For removing only one index in Tab Bar Controller(you can use this method in viewDidLoad and viewDidAppear both of them)
override func viewDidAppear(_ animated: Bool) {
}
override func viewDidLoad() {
super.viewDidLoad()
}
tabBarController.viewControllers?.remove(at:0) // for 0 index
tabBarController.viewControllers?.remove(at:1) // for 1 index
tabBarController.viewControllers?.remove(at:2) // for 2 index
if you have 4 index in Tab Bar and you want to remove the last 2 index
tabBarController.viewControllers?.remove(at:2)
tabBarController.viewControllers?.remove(at:2)
first line will remove the index 3rd one and you will remaining 3 from 4 and again when you remove the 2nd index it will remove again 3rd index and then you will have remain 2 index in last.
Another Way
//MARK: - Function Call
removeTab(at: 4)
//MARK: - Method
func removeTab(at index: Int) {
if self.viewControllers?.count ?? 0 >= index {
self.viewControllers?.remove(at: index)
}
}
Swift 4.1
For removing More items Use Array
let index = [2,0]
index.forEach{viewControllers?.remove(at: $0)}
the point in the array is You Should Use Descending Order of indexes to remove to get the desired Result.
better way is to use only text instead of image. choose 'space' as the text then disable that. then the user will not be able to see it. i haven't tested it but I sure it will work.
Related
OK. This answer helps a lot. I can select an accessibility item when a screen is shown. I simply add
UIAccessibility.post(notification: .layoutChanged, argument: <a reference to the UI item to receive focus>)
to the end of my viewWillAppear() method, and the item receives focus.
However, in one of my screens, the item I want to receive focus is a UISegmentedControl, and, when focused, it always selects the first item, no matter which one is selected. Since I followed the excellent suggestion here, I have an accessibility label for each item in the control, and I'd like my focus to begin on whichever segment is selected.
Is there a way to do this? As a rule, I try to avoid "hacky" solutions (like the one I just referenced), but I'm willing to consider anything.
Thanks!
UPDATE: Just to add insult to injury, I am also having an issue with the item I want selected being selected, then a second later, the screen jumps the selection to the first item. That's probably a topic for a second question.
I created a blank project as follows to reproduce the problem:
The solution is taking the selectedIndex to display the selected segment and providing the appropriate segment object for the VoiceOver notification: easy, isn't it?
I naively thought that getting the subview in the segmented control subviews array with the selectedIndex would do the job but that's definitely not possible because the subviews can move inside this array as the following snapshot highlights (red framed first element for instance):
The only way to identify a unique segment is its frame, so I pick up the segmented control index and the frame of the selected segment to pass them to the previous view controller.
That will allow to display (index) and read out (frame that identifies the object for the notification) the appropriate selected segment when this screen will appear after the transition.
Hereafter the code snippets for the view controller that contains the 'Next Screen' button:
class SOFSegmentedControl: UIViewController, UpdateSegmentedIndexDelegate {
var segmentIndex = 0
var segmentFrame = CGRect.zero
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let segueName = segue.identifier {
if (segueName == "SegmentSegue") {
if let destVC = segue.destination as? SOFSegmentedControlBis {
destVC.delegate = self
destVC.segmentIndex = segmentIndex
destVC.segmentFrame = segmentFrame
}
}
}
}
#IBAction func buttonAction(_ sender: UIButton) { self.performSegue(withIdentifier: "SegmentSegue", sender: sender) }
func updateSegmentIndex(_ index: Int, withFrame frame: CGRect) {
segmentIndex = index
segmentFrame = frame
}
}
... and for the view controller that displays the segmented control:
protocol UpdateSegmentedIndexDelegate: class {
func updateSegmentIndex(_ index: Int, withFrame frame: CGRect)
}
class SOFSegmentedControlBis: UIViewController {
#IBOutlet weak var mySegmentedControl: UISegmentedControl!
var delegate: UpdateSegmentedIndexDelegate?
var segmentFrame = CGRect.zero
var segmentIndex = 0
var segmentFrames = [Int:CGRect]()
override func viewDidLoad() {
super.viewDidLoad()
mySegmentedControl.addTarget(self,
action: #selector(segmentedControlValueChanged(_:)),
for: .valueChanged)
mySegmentedControl.selectedSegmentIndex = segmentIndex
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print(mySegmentedControl.subviews)
let sortedFrames = mySegmentedControl.subviews.sorted(by: { $0.frame.origin.x < $1.frame.origin.x})
for (index, segment) in sortedFrames.enumerated() { segmentFrames[index] = segment.frame }
if (self.segmentFrame == CGRect.zero) {
UIAccessibility.post(notification: .screenChanged,
argument: mySegmentedControl)
} else {
mySegmentedControl.subviews.forEach({
if ($0.frame == self.segmentFrame) {
UIAccessibility.post(notification: .screenChanged,
argument: $0)
}
})
}
}
#objc func segmentedControlValueChanged(_ notif: NSNotification) {
delegate?.updateSegmentIndex(mySegmentedControl.selectedSegmentIndex,
withFrame: segmentFrames[mySegmentedControl.selectedSegmentIndex]!) }
}
The final result is as follows:
Double tap to go to the next screen.
Select the next element to focus the second segment.
Double tap to select the focused element.
Get back to the previous screen thanks to the Z gesture natively known by iOS with the navigation controller. The delegate passes the index and the frame of the selected segment.
Double tap to go to the next screen.
The segment that was formerly selected is read out by VoiceOver and still selected.
You can now Focus Accessibility On A Particular Segment in A UISegmentedControl following this rationale.
I try to avoid "hacky" solutions (like the one I just referenced), but I'm willing to consider anything.
Unfortunately, this solution is a hacky one... sorry. However, it works and I couldn't find another one anywhere else: see it as a personal fix unless you get a cleaner one to share? ;o)
UPDATE... That's probably a topic for a second question.
I can't reproduce the behavior of your update: if you create a dedicated topic for this problem, please add the most detailed code and context so as to provide the most accurate solution.
i think this works~!
class VC {
let segment = UISegmentedControl()
func fucusSegment(index: Int) {
let item = segment.accessibilityElement(at: index )
UIAccessibility.post(notification: .layoutChanged, argument: item)
}
}
I have 5 buttons within a UIStackView, and I want to find out which index is being selected, and later compare those indexes. My code right now gives me an Array.Index. I've tried both subviews and arrangedSubviews. Is there anyway I can turn this into an Integer? I can't figure it out. Thanks!!
if let selectedIndex = stackview.subviews.index(of: sender) {
}
// UPDATE
I kinda got what I wanted with:
let int = stackview.subviews.distance(from: stackview.subviews.startIndex, to: selectedIndex)
I'm still not sure if this is the most efficient way, but it does the job for now.
index(of:) return Int.
Also you should find your button in the arrangedSubviews, not in the subviews
Assuming your stack view contains only buttons, and each button is connected to this #IBAction, this should work:
#IBAction func didTap(_ sender: Any) {
// make sure the sender is a button
guard let btn = sender as? UIButton else { return }
// make sure the button's superview is a stack view
guard let stack = btn.superview as? UIStackView else { return }
// get the array of arranged subviews
let theArray = stack.arrangedSubviews
get the "index" of the tapped button
if let idx = theArray.index(of: btn) {
print(idx)
} else {
print("Should never fail...")
}
}
I would add a tag to each of your buttons (button.tag = index) then check the tag of your sender.
So then you can wire up each of your buttons to the same function with a sender parameter, then check if sender.tag == index.
I have an item in my tab bar that shouldn't be enabled until certain conditions are met. I can disable that item in viewDidLoad() from my subclassed UITabBarController, but I'm having trouble creating a function that I can call when needed. Below is what I have so far - for reasons I don't understand, my tab bar item array is always nil! (Unless its initialized in viewDidLoad() where it works fine.)
func setTabState(whichTab: Int) {
let arrayOfTabBarItems = self.tabBar.items
if let barItems = arrayOfTabBarItems {
if barItems.count > 0 {
let tabBarItem = barItems[whichTab]
tabBarItem.isEnabled = !tabBarItem.isEnabled
}
}
}
Please put below code where you want to disable tabbar item in your UITabbarController class
//Here Disable 0 Tabbar item
DispatchQueue.main.async {
let items = self.tabBar.items!
if items.count > 0 {
items[0].isEnabled = false
}
}
The solution turned out to be a combination of Rohit Makwana's answer and some experimentation:
In viewDidLoad() of my CustomTabBarViewController I used Rohit's
answer to set the initial state of the tab bar items. I still don't understand why using DispatchQueue is necessary, but one thing at a time.
In a separate view controller I adopted the UITabBarControllerDelegate protocol and set
tabBar?.delegate = self.
Finally, I created a property observer on a variable that gets set to true when certain conditions are met:
var allButtonsPressed = false {
didSet {
if let items = tabBarController?.tabBar.items {
items[1].isEnabled = allButtonsPressed
}
}
}
And it works! When allButtonsPressed is true, the tab bar item is instantly enabled. When it's false - disabled. Plus one to Rohit for helping me get to the solution. Now, off to learn more about DispatchQueue...
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.
I am working with swift in Xcode 7. I am totally new to Swift, Xcode, and Firebase. I would like to have three UITableViewControllers in my iOS app. The first two TableView controllers will need dynamic content and the third TableView controller will need static content. I would like for the second and third TableView controllers to display data based on what is pressed on the previous TableView controller. All of the data will come from my Firebase. I have no idea where to start. Please point me in the right direction! Thank you!
This question is broad, in that it asks how to do three different tasks.
I think you'll be better off getting answers if you only ask for one thing at a time.
I can help you with populating a UITableViewController with Firebase.
class MyTableViewController: UITableViewController {
// your firebase reference as a property
var ref: Firebase!
// your data source, you can replace this with your own model if you wish
var items = [FDataSnapshot]()
override func viewDidLoad() {
super.viewDidLoad()
// initialize the ref in viewDidLoad
ref = Firebase(url: "<my-firebase-app>/items")
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// listen for update with the .Value event
ref.observeEventType(.Value) { (snapshot: FDataSnapshot!) in
var newItems = [FDataSnapshot]()
// loop through the children and append them to the new array
for item in snapshot.children {
newItems.append(item as! FDataSnapshot)
}
// replace the old array
self.items = newItems
// reload the UITableView
self.tableView.reloadData()
}
}
}
This technique uses the .Value event, you can also use .ChildAdded, but then you have to keep track of .ChildChanged, '.ChildRemoved', and .ChildMoved, which can get pretty complicated.
The FirebaseUI library for iOS handles this pretty easily.
dataSource = FirebaseTableViewDataSource(ref: self.firebaseRef, cellReuseIdentifier: "<YOUR-REUSE-IDENTIFIER>", view: self.tableView)
dataSource.populateCellWithBlock { (cell: UITableViewCell, obj: NSObject) -> Void in
let snap = obj as! FDataSnapshot
// Populate cell as you see fit, like as below
cell.textLabel?.text = snap.key as String
}
I do it slightly different when I have a UITableViewController, especially for those that can push to another detail view / or show a modal view over the top.
Having the setObserver in ViewDidAppear works well. However, I didnt like the fact that when I looked into a cells detail view and subsequently popped that view, I was fetching from Firebase and reloading the table again, despite the possibility of no changes being made.
This way the observer is added in viewDidLoad, and is only removed when itself is popped from the Nav Controller stack. The tableview is not reloaded unnecessarily when the viewAppears.
var myRef:FIRDatabaseReference = "" // your reference
override func viewDidLoad() {
super.viewDidLoad()
setObserver()
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
// only called when popped from the Nav Controller stack
// if I push to another detail view my observer will remain active
if isBeingDismissed() || isMovingFromParentViewController() {
myRef.removeAllObservers()
}
}
func setObserver() {
myRef.observeEventType(.Value, withBlock: { snapshot in
var newThings = [MyThing]()
for s in snapshot.children {
let ss = s as! FIRDataSnapshot
let new = MyThing(snap: ss) // convert my snapshot into my type
newThings.append(new)
}
self.things = newThings.sort{ $0.name < $1.name) } // some sort
self.tableView.reloadData()
})
}
I also use .ChildChanged .ChildDeleted and .ChildAdded in UITableViews. They work really well and allow you use UITableView animations. Its a little more code, but nothing too difficult.
You can use .ChildChanged to get the initial data one item at a time, then it will monitor for changes after that.
If you want all your data at once in the initial load you will need .Value, I suggest you use observeSingleEventOfType for your first load of the tableview. Just note that if you also have .ChildAdded observer you will also get an initial set of data when that observer is added - so you need to deal with those items (i.e. don't add them to your data set) otherwise your items will appear twice on the initial load.