Multiple lines in UITabBarItem Label - ios

I've tried many things but impossible to find a way to put the label of a UITabBarItem with a lineNumber customised.0 (i would like to get the title on 2 lines).
Is there a way to do it?

Now it contains two subviews. At 0 it is imageView and at 1 it is label.
Now make the height of imageview a bit smaller so that you can give the height of label a bit larger to have multiple lines. Set the property ofnumberoflines of the label to 0 via code.
let viewTabBar = tabBarItem.value(forKey: "view") as? UIView
let imageView = viewTabBar?.subviews[0] as? UIImageView
let label = viewTabBar?.subviews[1] as? UILabel
and now play with this label.

More stable solution:
guard let view = tabBarItem?.value(forKey: "view") as? UIView,
let label = (view.subviews.flatMap{ $0 as? UILabel }).first,
let imageView = (view.subviews.flatMap{ $0 as? UIImageView }).first else { return }

this is my solution for that
we need to implement this inside viewwillLayoutSubviews. to update the ui and make it works
in this example in going to customize my 3rd tab bar item only
override func viewWillLayoutSubviews() {
// acess to list of tab bar items
if let items = self.tabBar.items {
// in each item we have a view where we find 2 subviews imageview and label
// in this example i would like to change
// access to item view
let viewTabBar = items[2].value(forKey: "view") as? UIView
// access to item subviews : imageview and label
if viewTabBar.subviews.count == 2 {
let label = viewTabBar?.subviews[1]as? UILabel
// here is the customization for my label 2 lines
label?.numberOfLines = 2
label?.textAlignment = .center
label!.text = "tab_point".localized
// here customisation for image insets top and bottom
items[2].imageInsets = UIEdgeInsets(top: 8, left: 0, bottom: -5, right: 0)
}
}
}
and here is the result

you can not do this with UIBarButtonItem because it doesn't have property titleView like UINavigationItem!
You can set only string as title and tab image! that's it!
If you have option to set label as titleview of tabbaritem then you can take label with numberofline 0 but here you can set string only!

tabBar.items?.forEach {
$0.imageInsets = UIEdgeInsets(top: 8, left: 0, bottom: -15, right: 0)
guard let view = $0.value(forKey: "view") as? UIView,
let label = (view.subviews.compactMap{ $0 as? UILabel }).first,
let imageView = (view.subviews.compactMap{ $0 as? UIImageView }).first else { return }
label.numberOfLines = 2
if CBServerManagerApi.sharedManager().currentUser.type == "Local" {
label.text = titles[element]
}
label.lineBreakMode = .byTruncatingTail
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.heightAnchor.constraint(equalToConstant: 30)
])
}

Related

Add overlay to cover entire screen(including status bar)

I have an overlay (UIImageView) which should have a transparent background and alpha. How can I set the imageview such that it covers the entire screen? Currently, it covers the screen but not the UIStatusBar. I am adding the view in AppDelegate's main window as a subview.
The code:
let overlay1 = UIImageView(image: UIImage(named: "overlay-image"))
overlay1.contentMode = .scaleAspectFit
overlay1.backgroundColor = UIColor.black
overlay1.alpha = 0.87
overlay1.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
overlay1.isUserInteractionEnabled = true
overlay1.layer.zPosition = 1
(UIApplication.shared.delegate as? AppDelegate).window.addSubview(overlay1)
After discussion in comments found that changing the backgroundColor of statusBar is the reason why your code is not working properly.
By printing the superView of statusBar I found that statusBar is not added on UIWindow instead it is on UIStatusBarWindow which is probably above the mainWindow.
Also please don't use force unwrapping it can be cause of crash. At last I put a guard to fetch the statusBar, to check if it responds to backgroundColor property, to fetch its superView and adding the overlay on this superView got it working.
Your check for respondToSelector is also wrong. See below code it works as per your requirement.
guard let statusBar = UIApplication.shared.value(forKey: "statusBar") as? UIView, statusBar.responds(to: NSSelectorFromString("backgroundColor")), let superView = statusBar.superview else {return}
statusBar.backgroundColor = UIColor.red
let overlay1 = UIImageView(image: UIImage(named: "overlay-image"))
overlay1.contentMode = .scaleAspectFit
overlay1.backgroundColor = UIColor.black
overlay1.alpha = 0.87
overlay1.frame = superView.bounds
overlay1.isUserInteractionEnabled = true
overlay1.layer.zPosition = 1
superView.addSubview(overlay1)
Note: Changing the statusBar color is not recommended. You can set its style to default or light.
Okay I just tried with something and it worked. Just use it in your ViewController like:
// You should be using viewDidAppear(_:)
override func viewDidAppear(_ animated: Bool) {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate, let window = appDelegate.window {
let windowFrame = window.frame
let imageView = UIImageView(frame: windowFrame)
imageView.image = #imageLiteral(resourceName: "TakeOff") // use your image
imageView.contentMode = .scaleAspectFit
imageView.backgroundColor = UIColor.green.withAlphaComponent(0.2) // replace green with the color you want
window.addSubview(imageView)
}
}
But, remember one thing. It's not a good idea to add an image view as
an overlay. You should always use a UIView as an overlay and add the
image view as a sub view to that overlay.
Screenshot:

How to set reorder control at top of the row when tableview is in edit mode in swift 3+

I want to change and set reorder control icon at top of the row in tableviewcell when tableview is in editmode.
After long try and hit I found this below solution.
for view in cell.subviews {
if String(describing: view.description).range(of:"Reorder") != nil {
for subview in view.subviews {
if subview.isKind(of: UIImageView.self) {
let subImgV = subview as! UIImageView
if subImgV.image != UIImage(named: "rearrangeIcon") {
subImgV.image = UIImage(named: "rearrangeIcon")
subview.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height); // First we need to increse its size as it is superview after that set imageView content mode left, right, top, bottom
subImgV.contentMode = .top
}
}
}
}
}

Swift: how to make search bar animate when focused? Used to work?

Ok, I feel as though a few versions of Xcode ago this was the default, and I have it working in a past project, just do not know why I cant get this search bar animation to happen with this search bar now in Xcode 9.
What I want to happen is when the search bar ISNT focused, the placeholder text is centered:
Then when its tapped and becomes first responder, the text is left aligned (the transition between the two seems to animate automatically):
I swear search bars used to do this without any customization. How can I achieve this? Right now in my tableview custom header I set up my search bar programmatically like this:
searchBar.placeholder = "Find an Episode."
searchBar.backgroundColor = UIColor.clear
searchBar.searchBarStyle = .minimal
searchBar.layer.shadowOpacity = 0.0
searchBar.barTintColor = thirdColorStr
searchBar.tintColor = thirdColorStr
searchBar.frame = CGRect(x: 0, y: 0, width: self.bounds.width, height: screenSize.height * (40/screenSize.height))
self.addSubview(searchBar)
// searchBar.center = CGPoint(x: self.bounds.width/2, y: (searchBar.bounds.height/2))
searchBar.center = CGPoint(x: self.bounds.width/2, y: (searchBar.bounds.height)*0.8)
searchBar.delegate = listVC
for subView in searchBar.subviews {
for subViewOne in subView.subviews {
if let textField = subViewOne as? UITextField {
subViewOne.backgroundColor = secondColorStr
//print("HEREE:", textField.bounds.width/screenSize.width)
//use the code below if you want to change the color of placeholder
if let t = textField.value(forKey: "placeholderLabel") as? UILabel
{
t.textColor = thirdColorStr
t.font = iphoneFontThin
t.tintColor = thirdColorStr
t.backgroundColor = UIColor.clear
}
// if let clearButton = textField.value(forKey: "_clearButton") as? UIButton {
// // Create a template copy of the original button image
// let img = UIImage(named: "clear")
// clearButton.setImage(img, for: .normal)
//
// // Finally, set the image color
// //clearButton.tintColor = .red
// }
if let t = searchBar.value(forKey: "searchField") as? UITextField
{
t.textColor = barColorStr
t.font = iphoneFontThin
t.backgroundColor = secondColorStr
Then
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
self.searchBar.showsCancelButton = true
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
//searchBar.showsCancelButton = true
resetCategory(newCat: overallCategory)
searchBar.text = ""
searchBar.resignFirstResponder() //kills keyboard
}
I cant find any differences between the code that produces and animation and the one above that does not.

Label not display in subview of UITableView

I am designing an iOS hamburger menu with a table view in swift3
I want to display the number of notifications in menu tab.
let pieNotifi = UIImageView(frame: CGRectMake(cell.contentView.frame.size.width-40, cell.contentView.frame.size.height/2-7, 30, 20))
let pieLabNotifi = UILabel(frame: CGRectMake(cell.contentView.frame.size.width-40, cell.contentView.frame.size.height/2-7, 30, 20))
let subviews = self.view.viewWithTag(100001)
if myGlobal.unreaddot != 0 { //just a count for notifcaiton
if subviews == nil {
pieNotifi.tag = 100001;
cell.addSubview(pieNotifi)
let pieShape = pieNotifi.layer;
pieShape.backgroundColor = UIColor.red.cgColor
pieShape.cornerRadius = pieShape.bounds.width / 4
pieLabNotifi.textColor = UIColor.white
pieLabNotifi.adjustsFontSizeToFitWidth = true;
pieLabNotifi.minimumScaleFactor = 0.5
pieLabNotifi.textAlignment = NSTextAlignment.center
pieNotifi.addSubview(pieLabNotifi)
pieLabNotifi.text = String(myGlobal.unreaddot)
}
else{
pieLabNotifi.text = String(myGlobal.unreaddot)
}
}else{
let subviews = cell.viewWithTag(100001)
subviews?.removeFromSuperview()
print("remove red dot")
}
however, it only display the red colour layer but do not have the label. refer to the picture shown below
enter image description here
I had tried to set
.sendSubview(to back:)
.bringSubview(toFront: )
.layer.zPosition = 1
all these key word not work for me.
Can anyone advise me to solve this problem?
You are not adding subview to UITableView. According to your code you are adding a subview to the cell of UITableView.
Try to add the subview to the content view of the cell, like:
cell.contentView.addSubView(pieNotifi)
i think the issue is with the positions of your label. You are giving your label x coordinate as cell.contentView.frame.size.width-40, but you want to add it inside the red image view. Changing the x position and y position to 0 (or whatever value you need )for your label will solve your problem. I have modified your code accordingly :
let pieNotifi = UIImageView(frame: CGRectMake(cell.contentView.frame.size.width-40, cell.contentView.frame.size.height/2-7, 30, 20))
let pieLabNotifi = UILabel(frame: CGRectMake(0,0, 30, 20))
let subviews = self.view.viewWithTag(100001)
if myGlobal.unreaddot != 0 { //just a count for notifcaiton
if subviews == nil {
pieNotifi.tag = 100001;
cell.addSubview(pieNotifi)
let pieShape = pieNotifi.layer;
pieShape.backgroundColor = UIColor.red.cgColor
pieShape.cornerRadius = pieShape.bounds.width / 4
pieLabNotifi.textColor = UIColor.white
pieLabNotifi.adjustsFontSizeToFitWidth = true;
pieLabNotifi.minimumScaleFactor = 0.5
pieLabNotifi.textAlignment = NSTextAlignment.center
pieNotifi.addSubview(pieLabNotifi)
pieLabNotifi.text = String(myGlobal.unreaddot)
}
else{
pieLabNotifi.text = String(myGlobal.unreaddot)
}
}else{
let subviews = cell.viewWithTag(100001)
subviews?.removeFromSuperview()
print("remove red dot")
}
Also try to see your label position in UiDebugger , you might discover that label is added at wrong position.

Remove tab bar item text, show only image

Simple question, how can I remove the tab bar item text and show only the image?
I want the bar items to like in the instagram app:
In the inspector in xcode 6 I remove the title and choose a #2x (50px) and a #3x (75px) image. However the image does not use the free space of the removed text. Any ideas how to achieve the same tab bar item image like in the instagram app?
You should play with imageInsets property of UITabBarItem. Here is sample code:
let tabBarItem = UITabBarItem(title: nil, image: UIImage(named: "more")
tabBarItem.imageInsets = UIEdgeInsets(top: 9, left: 0, bottom: -9, right: 0)
Values inside UIEdgeInsets depend on your image size. Here is the result of that code in my app:
// Remove the titles and adjust the inset to account for missing title
for(UITabBarItem * tabBarItem in self.tabBar.items){
tabBarItem.title = #"";
tabBarItem.imageInsets = UIEdgeInsetsMake(6, 0, -6, 0);
}
Here is how you do it in a storyboard.
Clear the title text, and set the image inset like the screenshot below
Remember the icon size should follow the apple design guideline
This means you should have 25px x 25px for #1x, 50px x 50px for #2x, 75px x 75px for #3x
Using approach with setting each UITabBarItems title property to ""
and update imageInsets won't work properly if in view controller self.title is set. For example if self.viewControllers of UITabBarController are embedded in UINavigationController and you need title to be displayed on navigation bar. In this case set UINavigationItems title directly using self.navigationItem.title, not self.title.
If you're using storyboards this would be you best option. It loops through all of the tab bar items and for each one it sets the title to nothing and makes the image full screen. (You must have added an image in the storyboard)
for tabBarItem in tabBar.items!
{
tabBarItem.title = ""
tabBarItem.imageInsets = UIEdgeInsetsMake(6, 0, -6, 0)
}
Swift version of ddiego answer
Compatible with iOS 11
Call this function in viewDidLoad of every first child of the viewControllers after setting title of the viewController
Best Practice:
Alternativelly as #daspianist suggested in comments
Make a subclass of like this class BaseTabBarController:
UITabBarController, UITabBarControllerDelegate and put this function
in the subclass's viewDidLoad
func removeTabbarItemsText() {
var offset: CGFloat = 6.0
if #available(iOS 11.0, *), traitCollection.horizontalSizeClass == .regular {
offset = 0.0
}
if let items = tabBar.items {
for item in items {
item.title = ""
item.imageInsets = UIEdgeInsets(top: offset, left: 0, bottom: -offset, right: 0)
}
}
}
iOS 11 throws a kink in many of these solutions, so I just fixed my issues on iOS 11 by subclassing UITabBar and overriding layoutSubviews.
class MainTabBar: UITabBar {
override func layoutSubviews() {
super.layoutSubviews()
// iOS 11: puts the titles to the right of image for horizontal size class regular. Only want offset when compact.
// iOS 9 & 10: always puts titles under the image. Always want offset.
var verticalOffset: CGFloat = 6.0
if #available(iOS 11.0, *), traitCollection.horizontalSizeClass == .regular {
verticalOffset = 0.0
}
let imageInset = UIEdgeInsets(
top: verticalOffset,
left: 0.0,
bottom: -verticalOffset,
right: 0.0
)
for tabBarItem in items ?? [] {
tabBarItem.title = ""
tabBarItem.imageInsets = imageInset
}
}
}
I used the following code in my BaseTabBarController's viewDidLoad.
Note that in my example, I have 5 tabs, and selected image will always be base_image + "_selected".
// Get tab bar and set base styles
let tabBar = self.tabBar;
tabBar.backgroundColor = UIColor.whiteColor()
// Without this, images can extend off top of tab bar
tabBar.clipsToBounds = true
// For each tab item..
let tabBarItems = tabBar.items?.count ?? 0
for i in 0 ..< tabBarItems {
let tabBarItem = tabBar.items?[i] as UITabBarItem
// Adjust tab images (Like mstysf says, these values will vary)
tabBarItem.imageInsets = UIEdgeInsetsMake(5, 0, -6, 0);
// Let's find and set the icon's default and selected states
// (use your own image names here)
var imageName = ""
switch (i) {
case 0: imageName = "tab_item_feature_1"
case 1: imageName = "tab_item_feature_2"
case 2: imageName = "tab_item_feature_3"
case 3: imageName = "tab_item_feature_4"
case 4: imageName = "tab_item_feature_5"
default: break
}
tabBarItem.image = UIImage(named:imageName)!.imageWithRenderingMode(.AlwaysOriginal)
tabBarItem.selectedImage = UIImage(named:imageName + "_selected")!.imageWithRenderingMode(.AlwaysOriginal)
}
Swift 4 approach
I was able to do the trick by implementing a function that takes a TabBarItem and does some formatting to it.
Moves the image a little down to make it be more centered and also hides the text of the Tab Bar.
Worked better than just setting its title to an empty string, because when you have a NavigationBar as well, the TabBar regains the title of the viewController when selected
func formatTabBarItem(tabBarItem: UITabBarItem){
tabBarItem.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0)
tabBarItem.setTitleTextAttributes([NSAttributedStringKey.foregroundColor:UIColor.clear], for: .selected)
tabBarItem.setTitleTextAttributes([NSAttributedStringKey.foregroundColor:UIColor.clear], for: .normal)
}
Latest syntax
extension UITabBarItem {
func setImageOnly(){
imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0)
setTitleTextAttributes([NSAttributedString.Key.foregroundColor:UIColor.clear], for: .selected)
setTitleTextAttributes([NSAttributedString.Key.foregroundColor:UIColor.clear], for: .normal)
}
}
And just use it in your tabBar as:
tabBarItem.setImageOnly()
Here is a better, more foolproof way to do this other than the top answer:
[[UITabBarItem appearance] setTitleTextAttributes:#{NSForegroundColorAttributeName: [UIColor clearColor]}
forState:UIControlStateNormal];
[[UITabBarItem appearance] setTitleTextAttributes:#{NSForegroundColorAttributeName: [UIColor clearColor]}
forState:UIControlStateHighlighted];
Put this in your AppDelegate.didFinishLaunchingWithOptions so that it affects all tab bar buttons throughout the life of your app.
A minimal, safe UITabBarController extension in Swift (based on #korgx9 answer):
extension UITabBarController {
func removeTabbarItemsText() {
tabBar.items?.forEach {
$0.title = ""
$0.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0)
}
}
}
Based on the answer of ddiego, in Swift 4.2:
extension UITabBarController {
func cleanTitles() {
guard let items = self.tabBar.items else {
return
}
for item in items {
item.title = ""
item.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0)
}
}
}
And you just need to call self.tabBarController?.cleanTitles() in your view controller.
Custom TabBar - iOS 13, Swift 5, XCode 11
TabBar items without text
TabBar items centered vertically
Rounded TabBar view
TabBar Dynamic position and frames
Storyboard based. It can be achieved easily programmatically too. Only 4 Steps to follow:
Tab Bar Icons must be in 3 sizes, in black color. Usually, I download from fa2png.io - sizes: 25x25, 50x50, 75x75. PDF image files do not work!
In Storyboard for the tab bar item set the icon you want to use through Attributes Inspector. (see screenshot)
Custom TabBarController -> New File -> Type: UITabBarController -> Set on storyboard. (see screenshot)
UITabBarController class
class RoundedTabBarViewController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Custom tab bar view
customizeTabBarView()
}
private func customizeTabBarView() {
let tabBarHeight = tabBar.frame.size.height
self.tabBar.layer.masksToBounds = true
self.tabBar.isTranslucent = true
self.tabBar.barStyle = .default
self.tabBar.layer.cornerRadius = tabBarHeight/2
self.tabBar.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner]
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let viewWidth = self.view.bounds.width
let leadingTrailingSpace = viewWidth * 0.05
tabBar.frame = CGRect(x: leadingTrailingSpace, y: 200, width: viewWidth - (2 * leadingTrailingSpace), height: 49)
}
}
Result
code:
private func removeText() {
if let items = yourTabBarVC?.tabBar.items {
for item in items {
item.title = ""
}
}
}
In my case, same ViewController was used in TabBar and other navigation flow. Inside my ViewController, I have set self.title = "Some Title" which was appearing in TabBar regardless of setting title nil or blank while adding it in tab bar. I have also set imageInsets as follow:
item.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0)
So inside my ViewController, I have handled navigation title as follow:
if isFromTabBar {
// Title for NavigationBar when ViewController is added in TabBar
// NOTE: Do not set self.title = "Some Title" here as it will set title of tabBarItem
self.navigationItem.title = "Some Title"
} else {
// Title for NavigationBar when ViewController is opened from navigation flow
self.title = "Some Title"
}
Based on all the great answers on this page, I've crafted another solution that also allows you to show the the title again. Instead of removing the content of title, I just change the font color to transparent.
extension UITabBarItem {
func setTitleColorFor(normalState: UIColor, selectedState: UIColor) {
self.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: normalState], for: .normal)
self.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: selectedState], for: .selected)
}
}
extension UITabBarController {
func hideItemsTitle() {
guard let items = self.tabBar.items else {
return
}
for item in items {
item.setTitleColorFor(normalState: UIColor(white: 0, alpha: 0), selectedState: UIColor(white: 0, alpha: 0))
item.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0)
}
}
func showItemsTitle() {
guard let items = self.tabBar.items else {
return
}
for item in items {
item.setTitleColorFor(normalState: .black, selectedState: .yellow)
item.imageInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
}
}
Easiest way and always works:
class TabBar: UITabBar {
override func layoutSubviews() {
super.layoutSubviews()
subviews.forEach { subview in
if subview is UIControl {
subview.subviews.forEach {
if $0 is UILabel {
$0.isHidden = true
subview.frame.origin.y = $0.frame.height / 2.0
}
}
}
}
}
}
make a subclass of UITabBarController and assign that to your tabBar , then in the viewDidLoad method place this line of code:
tabBar.items?.forEach({ (item) in
item.imageInsets = UIEdgeInsets.init(top: 8, left: 0, bottom: -8, right: 0)
})
If you are looking to center the tabs / change the image insets without using magic numbers, the following has worked for me (in Swift 5.2.2):
In a UITabBarController subclass, you can add add the image insets after setting the view controllers.
override var viewControllers: [UIViewController]? {
didSet {
addImageInsets()
}
}
func addImageInsets() {
let tabBarHeight = tabBar.frame.height
for item in tabBar.items ?? [] where item.image != nil {
let imageHeight = item.image?.size.height ?? 0
let inset = CGFloat(Int((tabBarHeight - imageHeight) / 4))
item.imageInsets = UIEdgeInsets(top: inset,
left: 0,
bottom: -inset,
right: 0)
}
}
Several of the options above list solutions for dealing with hiding the text.

Resources