Swift - self.bounds.size.width doesn't return the correct width - ios

In another thread I found a solution for an underline for a segmented control.
The important line for my problem seems to be this one:
let underlineWidth: CGFloat = self.bounds.size.width / CGFloat(self.numberOfSegments)
It turns out that the calculation of the width is not entirely correct. Since I have always 3 segments I expected this value to be a third of the screen width (regardless of the device).
On this screenshot you can see the application running on two different devices:
Screenshot
As you can see, on the iPhone 6S, the width of the underline is slightly too big, where as on the iPhone 8Plus it's too small.
That can only mean that self.bounds.size.width doesn't return the correct width.
The whole class for the segmented control:
import UIKit
import Foundation
extension UISegmentedControl{
func removeBorder(){
let backgroundImage = UIImage.getColoredRectImageWith(color: UIColor.clear.cgColor, andSize: self.bounds.size)
let backgroundImageTest = UIImage.getColoredRectImageWith(color: UIColor.red.cgColor, andSize: self.bounds.size)
self.setBackgroundImage(backgroundImage, for: .normal, barMetrics: .default)
self.setBackgroundImage(backgroundImageTest, for: .selected, barMetrics: .default)
self.setBackgroundImage(backgroundImage, for: .highlighted, barMetrics: .default)
let deviderImage = UIImage.getColoredRectImageWith(color: UIColor.clear.cgColor, andSize: CGSize(width: 1.0, height: self.bounds.size.height))
self.setDividerImage(deviderImage, forLeftSegmentState: .selected, rightSegmentState: .normal, barMetrics: .default)
self.setTitleTextAttributes([NSAttributedStringKey.foregroundColor: UIColor.gray], for: .normal)
self.setTitleTextAttributes([NSAttributedStringKey.foregroundColor: UIColor(red: 67/255, green: 129/255, blue: 244/255, alpha: 1.0)], for: .selected)
}
func addUnderlineForSelectedSegment(){
removeBorder()
let underlineWidth: CGFloat = self.bounds.size.width / CGFloat(self.numberOfSegments)
let underlineHeight: CGFloat = 2.0
let underlineXPosition = CGFloat(selectedSegmentIndex * Int(underlineWidth))
let underLineYPosition = self.bounds.size.height - 1.0
let underlineFrame = CGRect(x: underlineXPosition, y: underLineYPosition, width: underlineWidth, height: underlineHeight)
let underline = UIView(frame: underlineFrame)
underline.backgroundColor = UIColor(red: 67/255, green: 129/255, blue: 244/255, alpha: 1.0)
underline.tag = 1
self.addSubview(underline)
}
func changeUnderlinePosition(){
guard let underline = self.viewWithTag(1) else {return}
let underlineFinalXPosition = (self.bounds.width / CGFloat(self.numberOfSegments)) * CGFloat(selectedSegmentIndex)
UIView.animate(withDuration: 0.1, animations: {
underline.frame.origin.x = underlineFinalXPosition
})
}
}
extension UIImage{
class func getColoredRectImageWith(color: CGColor, andSize size: CGSize) -> UIImage{
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
let graphicsContext = UIGraphicsGetCurrentContext()
graphicsContext?.setFillColor(color)
let rectangle = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)
graphicsContext?.fill(rectangle)
let rectangleImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return rectangleImage!
}
}

It is possible that you are trying to obtain dimensions in the method func viewDidLoad().
Try to take the exact dimensions in the method func viewDidLayoutSubviews().
See also this answer

The exact size of the view is only correct after the view has rendered. Before this has been done the view has a default size (e.g. as in the xib or storyboard).
In viewDidLayoutSubviews() or viewDidAppear()/viewWillAppear() the size will definitely be calculated right.

Related

Swift 4: How to add a circle point as a selection indicator to a UITabbarItem

I want to display a little circle point as indicator to the selected UITabBarItem. How can i do this?
I use a custom UITabBarController. It looks like this:
import UIKit
class EventTabBar: UITabBarController {
override func awakeFromNib() {
tabBar.barTintColor = UIColor.white
tabBar.tintColor = UIColor(red: 79/255, green: 122/255, blue: 198/255, alpha: 1)
tabBar.unselectedItemTintColor = UIColor(red: 198/255, green: 203/255, blue: 209/255, alpha: 1)
tabBar.isTranslucent = false
tabBar.shadowImage = UIImage()
tabBar.backgroundImage = UIImage()
//Add Shadow to TabBar
tabBar.layer.shadowOpacity = 0.12
tabBar.layer.shadowOffset = CGSize(width: 0, height: 2)
tabBar.layer.shadowRadius = 8
tabBar.layer.shadowColor = UIColor.black.cgColor
tabBar.layer.masksToBounds = false
}
}
Can i use the selectionIndicatorImage to do this?
Hope you can help me. Thanks for your answer
let size = CGSize(width: tabController.tabBar.frame.width / (amount of items),
height: tabController.tabBar.frame.height)
let dotImage = UIImage().createSelectionIndicator(color: .blue, size: size, lineHeight: 7)
tabController.tabBar.selectionIndicatorImage = dotImage
-
extension UIImage {
func createSelectionIndicator(color: UIColor, size: CGSize, lineHeight: CGFloat) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, false, 0)
color.setFill()
let innerRect = CGRect(x: (size.width/2) - lineHeight/2,
y: size.height - lineHeight - 2,
width: lineHeight,
height: lineHeight)
let path = UIBezierPath(roundedRect: innerRect, cornerRadius: lineHeight/2)
path.fill()
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
It's pretty easy actually. TabBarButton has two properties to set image, one is TabBarItem.image and another is TabBarItem.selectedImage set an image without the circle point for TabBarItem.image property and set an image with circle point for TabBarItem.selectedImage property.
If you want to set only the circle point for selected state, set the normal image property to UIImage(). Hope this solves the problem.
scanTabBarItem.image = UIImage.fontAwesomeIcon(name: .qrcode, textColor: .white, size: CGSize(width: 30, height: 30))
scanTabBarItem.selectedImage = UIImage.fontAwesomeIcon(name: .addressBook, textColor: .white, size: CGSize(width: 30, height: 30))
if not to show any image normally,
scanTabBarItem.image = UIImage()

Adding custom border to UISegmentControl

I'm trying to customize my segement control like below image. So, far I was able to customize its text attributes and color. Only problem is with the border. As per the below image, if my first segment is selected the border should apply to first segment top, right and second segment's bottom. And if my second segment is selected it should be the reverse ie, second segment top, left and first segments bottom.
Segment Model Image
Things done so far
UISegmentedControl.appearance().setTitleTextAttributes([NSAttributedStringKey.foregroundColor: UIColor.blue], for: .selected)
UISegmentedControl.appearance().setTitleTextAttributes([NSAttributedStringKey.foregroundColor: UIColor.green], for: .normal)
You can do this by adding an extension to UISegmentedControl. Try this.
extension UISegmentedControl {
private func defaultConfiguration(font: UIFont = UIFont.boldSystemFont(ofSize: 12), color: UIColor = UIColor.gray) {
let defaultAttributes = [
NSAttributedStringKey.font.rawValue: font,
NSAttributedStringKey.foregroundColor.rawValue: color
]
setTitleTextAttributes(defaultAttributes, for: .normal)
}
private func selectedConfiguration(font: UIFont = UIFont.boldSystemFont(ofSize: 12), color: UIColor = UIColor.blue) {
let selectedAttributes = [
NSAttributedStringKey.font.rawValue: font,
NSAttributedStringKey.foregroundColor.rawValue: color
]
setTitleTextAttributes(selectedAttributes, for: .selected)
}
private func removeBorder(){
let backgroundImage = getColoredRectImageWith(color: UIColor.white.cgColor, andSize: CGSize(width: self.bounds.size.width, height: self.bounds.size.height), yOffset: 2)
let backgroundImage2 = getColoredRectImageWith(color: UIColor.lightGray.cgColor, andSize: CGSize(width: self.bounds.size.width, height: self.bounds.size.height))
self.setBackgroundImage(backgroundImage2, for: .normal, barMetrics: .default)
self.setBackgroundImage(backgroundImage, for: .selected, barMetrics: .default)
self.setBackgroundImage(backgroundImage, for: .highlighted, barMetrics: .default)
let deviderImage = getColoredRectImageWith(color: UIColor.gray.cgColor, andSize: CGSize(width: 1.0, height: self.bounds.size.height))
self.setDividerImage(deviderImage, forLeftSegmentState: .selected, rightSegmentState: .normal, barMetrics: .default)
defaultConfiguration( color: UIColor.green)
selectedConfiguration(color: UIColor.blue)
}
func addUnderlineForSelectedSegment(){
removeBorder()
let underlineWidth: CGFloat = self.bounds.size.width / CGFloat(self.numberOfSegments)
let underlineHeight: CGFloat = 1.0
let underlineXPosition = CGFloat(selectedSegmentIndex * Int(underlineWidth))
let underLineYPosition = self.bounds.size.height - 2.0
let underlineFrame = CGRect(x: underlineXPosition, y: underLineYPosition, width: underlineWidth, height: underlineHeight)
let topUnderline = UIView(frame: underlineFrame)
topUnderline.backgroundColor = UIColor.gray
topUnderline.tag = 1
topUnderline.frame.origin.y = self.frame.origin.y
self.addSubview(topUnderline)
let bottomUnderline = UIView(frame: underlineFrame)
bottomUnderline.backgroundColor = UIColor.gray
bottomUnderline.tag = 2
bottomUnderline.frame.origin.x = topUnderline.frame.maxX
self.addSubview(bottomUnderline)
}
func changeUnderlinePosition(){
guard let topUnderline = self.viewWithTag(1) else {return}
let topUnderlineFinalXPosition = (self.bounds.width / CGFloat(self.numberOfSegments)) * CGFloat(selectedSegmentIndex)
topUnderline.frame.origin.x = topUnderlineFinalXPosition
guard let bottomUnderline = self.viewWithTag(2) else {return}
let underlineFinalXPosition = (selectedSegmentIndex == 0) ? topUnderline.frame.maxX : self.frame.origin.x
bottomUnderline.frame.origin.x = underlineFinalXPosition
}
private func getColoredRectImageWith(color: CGColor, andSize size: CGSize,yOffset:CGFloat = 0, hOffset:CGFloat = 0) -> UIImage{
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
let graphicsContext = UIGraphicsGetCurrentContext()
graphicsContext?.setFillColor(color)
let rectangle = CGRect(x: 0.0, y: 0.0 + yOffset, width: size.width, height: size.height - hOffset)
graphicsContext?.fill(rectangle)
let rectangleImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return rectangleImage!
}
}
Usage
In viewDidLoad add
mySegmentControl.addUnderlineForSelectedSegment()
And in your segment control action use
#IBAction func mySegmentControl(_ sender: UISegmentedControl) {
mySegmentControl.changeUnderlinePosition()
}

UIButton colors have gradient in iphone 8 plus and iphone X (iOS version independent issue)

I have used simple UIButton with my usecase being: 3 different background colors for states - Normal, Highlighted and Disabled. I have achieved this by the following code:
#IBOutlet var myButton: UIButton!{
didSet{
myButton.setBackgroundImage(UIImage.imageWithColor(color: #colorLiteral(red: 0, green: 0.3803921569, blue: 0.6196078431, alpha: 1)), for: .normal)
myButton.setBackgroundImage(UIImage.imageWithColor(color: #colorLiteral(red: 0, green: 0.4745098039, blue: 0.7725490196, alpha: 1)), for: .highlighted)
myButton.setBackgroundImage(UIImage.imageWithColor(color: .gray), for: .disabled)
}
}
Extension function for UIImage---
class func imageWithColor(color: UIColor) -> UIImage {
let rect = CGRect(x: 0.0, y: 0.0, width: 1.0, height: 0.5)
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
color.setFill()
UIRectFill(rect)
let image : UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
But my button appears as follows in iphone8 and iphone X
Instead of gray, it is giving me a gradient of gray and blue
Recently, I have faced with this issue too, using exactly the same code. Solution was pretty simple: in your extension, change CGRect height value to 1.0 instead of 0.5. Now, everything will be rendered properly on every device. Strange issue, maybe somebody has ideas, why it works that way?
Your updated extension code:
class func imageWithColor(color: UIColor) -> UIImage {
let rect = CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0)
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
color.setFill()
UIRectFill(rect)
let image : UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
func addGradientToBackground() {
let layer = CAGradientLayer()
layer.frame = CGRect(origin: .zero, size: self.frame.size)
layer.colors = [color1, color2, color3]
layer.startPoint = CGPoint(x: 0.0, y: 0.0)
layer.endPoint = CGPoint(x: 1.0, y: 0.0)
view.layer.insertSublayer(layer, at: 0)
}
Instead of color1, color2, color3 you can add your color respectively.
If you aiming to let the button to has a solid background color, you would need to change:
myButton.setBackgroundImage(UIImage.imageWithColor(color: .gray), for: .disabled)
Note that this line of code doesn't compile for me.
to:
myButton.backgroundColor = .gray
There is no need to set a background image for the button in your case, instead you should change directly the button background color (solid).

How to display only bottom border for selected item in UISegmentedControl?

I'm extremely new to iOS development and ran into some trouble while building an app for a course.
I created a segmented control and its init function (shown below) is being called in the view controller class containing the segmented control. I was able to remove all borders and dividers of the segmented control from the segmented control class as follows:
import Foundation
import UIKit
class CashSegmentedControl: UISegmentedControl{
func initUI(){
removeBorders()
}
func removeBorders(){
self.tintColor = UIColor.clear
}
I want it to have a line under each segment WHEN the segment is selected (similar to instagram)
I've searched a lot and come across some posts on StackOverflow but they seem to be for older versions of Swift. I'd really appreciate any help in this matter, and if there is a better solution for customising the borders (other than what I have done), I'd love to learn more!
Thanks a lot :)
Add the following code in a separate swift file (command+N -> New File):
extension UISegmentedControl{
func removeBorder(){
let backgroundImage = UIImage.getColoredRectImageWith(color: UIColor.white.cgColor, andSize: self.bounds.size)
self.setBackgroundImage(backgroundImage, for: .normal, barMetrics: .default)
self.setBackgroundImage(backgroundImage, for: .selected, barMetrics: .default)
self.setBackgroundImage(backgroundImage, for: .highlighted, barMetrics: .default)
let deviderImage = UIImage.getColoredRectImageWith(color: UIColor.white.cgColor, andSize: CGSize(width: 1.0, height: self.bounds.size.height))
self.setDividerImage(deviderImage, forLeftSegmentState: .selected, rightSegmentState: .normal, barMetrics: .default)
self.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.gray], for: .normal)
self.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor(red: 67/255, green: 129/255, blue: 244/255, alpha: 1.0)], for: .selected)
}
func addUnderlineForSelectedSegment(){
removeBorder()
let underlineWidth: CGFloat = self.bounds.size.width / CGFloat(self.numberOfSegments)
let underlineHeight: CGFloat = 2.0
let underlineXPosition = CGFloat(selectedSegmentIndex * Int(underlineWidth))
let underLineYPosition = self.bounds.size.height - 1.0
let underlineFrame = CGRect(x: underlineXPosition, y: underLineYPosition, width: underlineWidth, height: underlineHeight)
let underline = UIView(frame: underlineFrame)
underline.backgroundColor = UIColor(red: 67/255, green: 129/255, blue: 244/255, alpha: 1.0)
underline.tag = 1
self.addSubview(underline)
}
func changeUnderlinePosition(){
guard let underline = self.viewWithTag(1) else {return}
let underlineFinalXPosition = (self.bounds.width / CGFloat(self.numberOfSegments)) * CGFloat(selectedSegmentIndex)
UIView.animate(withDuration: 0.1, animations: {
underline.frame.origin.x = underlineFinalXPosition
})
}
}
extension UIImage{
class func getColoredRectImageWith(color: CGColor, andSize size: CGSize) -> UIImage{
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
let graphicsContext = UIGraphicsGetCurrentContext()
graphicsContext?.setFillColor(color)
let rectangle = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)
graphicsContext?.fill(rectangle)
let rectangleImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return rectangleImage!
}
}
Then after call segmentedControl.addUnderlineForSelectedSegment() from your viewDidLoad() method, and create an #IBAction method for the segmented control like so:
#IBAction func segmentedControlDidChange(_ sender: UISegmentedControl){
segmentedControl.changeUnderlinePosition()
}
Then call segmentedControl.changeUnderlinePosition() from within this method.
Do not forget to connect the segmented control from your storyboard to the #IBAction method you just created.
Very important: Don't forget to use Auto layout in the storyboard to determine the size and position of your segmented control.
This is the result:
Feel free to ask any other questions you may have :)
A.Jam's answer in Xcode 9.3, Swift 4.2 version
import UIKit
extension UISegmentedControl {
func removeBorder(){
let backgroundImage = UIImage.getColoredRectImageWith(color: UIColor.white.cgColor, andSize: self.bounds.size)
self.setBackgroundImage(backgroundImage, for: .normal, barMetrics: .default)
self.setBackgroundImage(backgroundImage, for: .selected, barMetrics: .default)
self.setBackgroundImage(backgroundImage, for: .highlighted, barMetrics: .default)
let deviderImage = UIImage.getColoredRectImageWith(color: UIColor.white.cgColor, andSize: CGSize(width: 1.0, height: self.bounds.size.height))
self.setDividerImage(deviderImage, forLeftSegmentState: .selected, rightSegmentState: .normal, barMetrics: .default)
self.setTitleTextAttributes([NSAttributedStringKey.foregroundColor: UIColor.gray], for: .normal)
self.setTitleTextAttributes([NSAttributedStringKey.foregroundColor: UIColor(red: 67/255, green: 129/255, blue: 244/255, alpha: 1.0)], for: .selected)
}
func addUnderlineForSelectedSegment(){
removeBorder()
let underlineWidth: CGFloat = self.bounds.size.width / CGFloat(self.numberOfSegments)
let underlineHeight: CGFloat = 2.0
let underlineXPosition = CGFloat(selectedSegmentIndex * Int(underlineWidth))
let underLineYPosition = self.bounds.size.height - 1.0
let underlineFrame = CGRect(x: underlineXPosition, y: underLineYPosition, width: underlineWidth, height: underlineHeight)
let underline = UIView(frame: underlineFrame)
underline.backgroundColor = UIColor(red: 67/255, green: 129/255, blue: 244/255, alpha: 1.0)
underline.tag = 1
self.addSubview(underline)
}
func changeUnderlinePosition(){
guard let underline = self.viewWithTag(1) else {return}
let underlineFinalXPosition = (self.bounds.width / CGFloat(self.numberOfSegments)) * CGFloat(selectedSegmentIndex)
UIView.animate(withDuration: 0.1, animations: {
underline.frame.origin.x = underlineFinalXPosition
})
}
}
extension UIImage {
class func getColoredRectImageWith(color: CGColor, andSize size: CGSize) -> UIImage{
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
let graphicsContext = UIGraphicsGetCurrentContext()
graphicsContext?.setFillColor(color)
let rectangle = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)
graphicsContext?.fill(rectangle)
let rectangleImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return rectangleImage!
}
}
you can use this
let segmentBottomBorder = CALayer()
segmentBottomBorder?.borderColor = UIColor.blue.cgColor
segmentBottomBorder?.borderWidth = 3
let x = CGFloat(sender.selectedSegmentIndex) * width
let y = sender.frame.size.height - (segmentBottomBorder?.borderWidth)!
let width: CGFloat = sender.frame.size.width/3
segmentBottomBorder?.frame = CGRect(x: x, y: y, width: width, height: (segmentBottomBorder?.borderWidth)!)
sender.layer.addSublayer(segmentBottomBorder!)
A.Jam's answer in Xcode 10.1, Swift 4.2 version - without UIImage Extension
and a grey underline for all segmentIndexes
extension UISegmentedControl {
func removeBorder(){
self.tintColor = UIColor.clear
self.backgroundColor = UIColor.clear
self.setTitleTextAttributes( [NSAttributedString.Key.foregroundColor : UIColor.orange], for: .selected)
self.setTitleTextAttributes( [NSAttributedString.Key.foregroundColor : UIColor.gray], for: .normal)
}
func setupSegment() {
self.removeBorder()
let segmentUnderlineWidth: CGFloat = self.bounds.width
let segmentUnderlineHeight: CGFloat = 2.0
let segmentUnderlineXPosition = self.bounds.minX
let segmentUnderLineYPosition = self.bounds.size.height - 1.0
let segmentUnderlineFrame = CGRect(x: segmentUnderlineXPosition, y: segmentUnderLineYPosition, width: segmentUnderlineWidth, height: segmentUnderlineHeight)
let segmentUnderline = UIView(frame: segmentUnderlineFrame)
segmentUnderline.backgroundColor = UIColor.gray
self.addSubview(segmentUnderline)
self.addUnderlineForSelectedSegment()
}
func addUnderlineForSelectedSegment(){
let underlineWidth: CGFloat = self.bounds.size.width / CGFloat(self.numberOfSegments)
let underlineHeight: CGFloat = 2.0
let underlineXPosition = CGFloat(selectedSegmentIndex * Int(underlineWidth))
let underLineYPosition = self.bounds.size.height - 1.0
let underlineFrame = CGRect(x: underlineXPosition, y: underLineYPosition, width: underlineWidth, height: underlineHeight)
let underline = UIView(frame: underlineFrame)
underline.backgroundColor = UIColor.orange
underline.tag = 1
self.addSubview(underline)
}
func changeUnderlinePosition(){
guard let underline = self.viewWithTag(1) else {return}
let underlineFinalXPosition = (self.bounds.width / CGFloat(self.numberOfSegments)) * CGFloat(selectedSegmentIndex)
underline.frame.origin.x = underlineFinalXPosition
}
}
To create WHITE segmented control in iOS13
segmentedControl.setTitleTextAttributes( [NSAttributedString.Key.foregroundColor : UIColor.black, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 17, weight: .medium)], for: .selected)
segmentedControl.setTitleTextAttributes( [NSAttributedString.Key.foregroundColor : UIColor.gray, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 17, weight: .medium)], for: .normal)
if #available(iOS 13.0, *) {
let dividerImage = UIImage(color: .white, size: CGSize(width: 1, height: 32))
segmentedControl.setBackgroundImage(UIImage(named: "w1"), for: .normal, barMetrics: .default)
segmentedControl.setBackgroundImage(UIImage(named: "w2"), for: .selected, barMetrics: .default)
segmentedControl.setDividerImage(dividerImage, forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)
segmentedControl.layer.cornerRadius = 0
segmentedControl.layer.masksToBounds = true
}
extension UIImage {
convenience init(color: UIColor, size: CGSize) {
UIGraphicsBeginImageContextWithOptions(size, false, 1)
color.set()
let ctx = UIGraphicsGetCurrentContext()!
ctx.fill(CGRect(origin: .zero, size: size))
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
self.init(data: image.pngData()!)!
}
}
Where w1 image is
and w2 image is
A.Jams answer for SWIFT 5.1 Xcode 11.3 to create GRAY control
import Foundation
extension UISegmentedControl {
func removeBorder(){
self.tintColor = UIColor.clear
self.backgroundColor = UIColor.clear
self.setTitleTextAttributes( [NSAttributedString.Key.foregroundColor : UIColor.stavkrugDarkBlue], for: .selected)
self.setTitleTextAttributes( [NSAttributedString.Key.foregroundColor : UIColor.gray], for: .normal)
if #available(iOS 13.0, *) {
self.selectedSegmentTintColor = UIColor.clear
}
}
func setupSegment() {
self.removeBorder()
let segmentUnderlineWidth: CGFloat = self.bounds.width
let segmentUnderlineHeight: CGFloat = 2.0
let segmentUnderlineXPosition = self.bounds.minX
let segmentUnderLineYPosition = self.bounds.size.height - 1.0
let segmentUnderlineFrame = CGRect(x: segmentUnderlineXPosition, y: segmentUnderLineYPosition, width: segmentUnderlineWidth, height: segmentUnderlineHeight)
let segmentUnderline = UIView(frame: segmentUnderlineFrame)
segmentUnderline.backgroundColor = UIColor.clear
self.addSubview(segmentUnderline)
self.addUnderlineForSelectedSegment()
}
func addUnderlineForSelectedSegment(){
let underlineWidth: CGFloat = self.bounds.size.width / CGFloat(self.numberOfSegments)
let underlineHeight: CGFloat = 2.0
let underlineXPosition = CGFloat(selectedSegmentIndex * Int(underlineWidth))
let underLineYPosition = self.bounds.size.height - 1.0
let underlineFrame = CGRect(x: underlineXPosition, y: underLineYPosition, width: underlineWidth, height: underlineHeight)
let underline = UIView(frame: underlineFrame)
underline.backgroundColor = UIColor.stavkrugDarkBlue
underline.tag = 1
self.addSubview(underline)
}
func changeUnderlinePosition(){
guard let underline = self.viewWithTag(1) else {return}
let underlineFinalXPosition = (self.bounds.width / CGFloat(self.numberOfSegments)) * CGFloat(selectedSegmentIndex)
underline.frame.origin.x = underlineFinalXPosition
}
}
My own implementation of this using the accepted Answer and some tidbits from others.
Changes:
it will honour Dark / Light mode appearance changes and the segmentedControl TextColor and BGColor will change accordingly. This will also get rid of the segmented control dividing lines.
I wrapped the setupSegment() and addUnderlineForSelectedSegment() within DispatchQueue.main.asyn() so that changes to phone orientation will get the correct width.
Added a new function called removeUnderline() to get rid of the previous underline before drawing a new one. This is so that the underline width will correspond to the actual width within that orientation.
Where to put stuffs.
viewDidLoad() {
yourUISegmentedControl.setupSegment()
}
/// https://stackoverflow.com/a/57943610/14414215
/// Use this to detect changes in User Interface (Dark or Light Mode) then setup the UISegmentedControl Appearance
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
yourUISegmentedControl.setupSegment()
}
Note: I did try to put into viewWillTransition() but this will only detect phone orientation changes and not Dark/Light mode appearance changes. Putting it into traitCollectionDidChange() will actually cater for both Dark/Light Mode & Orientation Changes.
The rest below you can just put Into it's own Swift File.
import UIKit
extension UISegmentedControl {
func removeBorder(){
var bgcolor: CGColor
var textColorNormal: UIColor
var textColorSelected: UIColor
if self.traitCollection.userInterfaceStyle == .dark {
bgcolor = UIColor.black.cgColor
textColorNormal = UIColor.gray
textColorSelected = UIColor.white
} else {
bgcolor = UIColor.white.cgColor
textColorNormal = UIColor.gray
textColorSelected = UIColor.black
}
let backgroundImage = UIImage.getColoredRectImageWith(color: bgcolor, andSize: self.bounds.size)
self.setBackgroundImage(backgroundImage, for: .normal, barMetrics: .default)
self.setBackgroundImage(backgroundImage, for: .selected, barMetrics: .default)
self.setBackgroundImage(backgroundImage, for: .highlighted, barMetrics: .default)
let deviderImage = UIImage.getColoredRectImageWith(color: bgcolor, andSize: CGSize(width: 1.0, height: self.bounds.size.height))
self.setDividerImage(deviderImage, forLeftSegmentState: .selected, rightSegmentState: .normal, barMetrics: .default)
self.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: textColorNormal], for: .normal)
self.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: textColorSelected], for: .selected)
}
func setupSegment() {
DispatchQueue.main.async() {
self.removeBorder()
// I Commented all these out as I didn't see what exactly it's used for as the color is clear anyways
// let segmentUnderlineWidth: CGFloat = self.bounds.width
// print("setupSegment UnderlineWidth:\(segmentUnderlineWidth) width:\(UIScreen.main.bounds.width) \(self.bounds.size.width)")
// let segmentUnderlineHeight: CGFloat = 10.0
// let segmentUnderlineXPosition = self.bounds.minX
// let segmentUnderLineYPosition = self.bounds.size.height - 4.0
// let segmentUnderlineFrame = CGRect(x: segmentUnderlineXPosition, y: segmentUnderLineYPosition, width: segmentUnderlineWidth, height: segmentUnderlineHeight)
// let segmentUnderline = UIView(frame: segmentUnderlineFrame)
// segmentUnderline.backgroundColor = UIColor.red
//
// self.addSubview(segmentUnderline)
self.addUnderlineForSelectedSegment()
}
}
func addUnderlineForSelectedSegment(){
DispatchQueue.main.async() {
self.removeUnderline()
let underlineWidth: CGFloat = self.bounds.size.width / CGFloat(self.numberOfSegments)
let underlineHeight: CGFloat = 10.0
let underlineXPosition = CGFloat(self.selectedSegmentIndex * Int(underlineWidth))
let underLineYPosition = self.bounds.size.height - 4.0
let underlineFrame = CGRect(x: underlineXPosition, y: underLineYPosition, width: underlineWidth, height: underlineHeight)
let underline = UIView(frame: underlineFrame)
underline.backgroundColor = UIColor.darkGray
underline.tag = 1
self.addSubview(underline)
}
}
func changeUnderlinePosition(){
guard let underline = self.viewWithTag(1) else {return}
let underlineFinalXPosition = (self.bounds.width / CGFloat(self.numberOfSegments)) * CGFloat(selectedSegmentIndex)
underline.frame.origin.x = underlineFinalXPosition
}
func removeUnderline(){
guard let underline = self.viewWithTag(1) else {return}
underline.removeFromSuperview()
}
}
extension UIImage{
class func getColoredRectImageWith(color: CGColor, andSize size: CGSize) -> UIImage{
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
let graphicsContext = UIGraphicsGetCurrentContext()
graphicsContext?.setFillColor(color)
let rectangle = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)
graphicsContext?.fill(rectangle)
let rectangleImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return rectangleImage!
}
}

Transparent navbar swift iOS

This is what the project currently looks like
And this is the code that I use
func styleNavBar() {
let navigationBarAppearace = UINavigationBar.appearance()
navigationBarAppearace.tintColor = UIColor(red:1.0, green:1.0, blue:1.0, alpha:1.0)
navigationBarAppearace.titleTextAttributes = [NSForegroundColorAttributeName:UIColor(red:1.00, green:1.00, blue:1.00, alpha:1.0)]
navigationBarAppearace.translucent = true
navigationBarAppearace.backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.3)
navigationBarAppearace.setBackgroundImage(UIImage(), forBarMetrics: .Default)}
I have tried to remove the following code, but then it looks like this.
navigationBarAppearace.setBackgroundImage(UIImage(), forBarMetrics: .Default)
My question is, how do I get the navbar to fill up to the top? and still have the same look
First, create an extension for UIImage which create image with solid color of specified size.
Keep this extension in any ViewController at class label
extension UIImage {
class func imageWithColor(color: UIColor, size: CGSize) -> UIImage {
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
UIGraphicsBeginImageContextWithOptions(size, false, 0)
color.setFill()
UIRectFill(rect)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
}
then use the below method to customise your navbar
func styleNavBar() {
let navigationBarAppearace = UINavigationBar.appearance()
navigationBarAppearace.tintColor = UIColor(red:1.0, green:1.0, blue:1.0, alpha:1.0)
navigationBarAppearace.titleTextAttributes = [NSForegroundColorAttributeName:UIColor(red:1.00, green:1.00, blue:1.00, alpha:1.0)]
navigationBarAppearace.isTranslucent = true
let colorImage = UIImage.imageWithColor(color: UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.3), size: CGSize(width: UIScreen.main.bounds.width, height: 64))
navigationBarAppearace.setBackgroundImage(colorImage, for: .default)
}
Hope this will solve your problem.
I'm using this code on my UINavigationBar to make it transparent. You can adjust it to your needs. In the picture there's a searchController in the titleView
if let topBar = self.navigationController?.navigationBar {
topBar.setBackgroundImage(UIImage(), forBarMetrics: .Default)
topBar.shadowImage = UIImage()
topBar.barTintColor = UIColor.clearColor()
topBar.tintColor = UIColor.whiteColor()
topBar.translucent = true
}
Here's what it looks like:

Resources