Center UIButton text for floating action button like Inbox app - ios

floatingButton = UIButton(x: blocx, y:blocy, width: imageSize, height: imageSize, target: self, action: "clickedFloatingButton")
floatingButton!.backgroundColor = CozyColors.StatCardSkip
floatingButton!.layer.cornerRadius = floatingButton!.frame.size.width / 2
floatingButton!.setTitle("+", forState: UIControlState.Normal)
floatingButton!.setTitleColor(CozyColors.ThemeWhite, forState: UIControlState.Normal)
floatingButton!.titleLabel?.font = UIFont(name: CozyStyles.ThemeFontName, size: imageSize*2/3)
view.addSubview(floatingButton!)
Here is the result:
As you can see the plus button is not aligned properly to the center. How can I put it right in the middle without adding a UILabel as a subview?

Try this code :
var floatingButton = UIButton(frame: CGRectMake(10, 20, 50, 50))
floatingButton.backgroundColor = UIColor.redColor()
floatingButton.layer.cornerRadius = floatingButton.frame.size.width / 2
floatingButton.setTitle("+", forState: UIControlState.Normal)
floatingButton.setTitleColor(UIColor.whiteColor(), forState: UIControlState.Normal)
floatingButton.titleLabel?.font = UIFont(name: floatingButton.titleLabel!.font.familyName , size: 50)
floatingButton.titleEdgeInsets = UIEdgeInsetsMake(-10, 0, 0, 0)
view.addSubview(floatingButton)

Well,I think a easier way is just set a background image for it
floatingButton.setBackgroundImage(UIImage(named: "icon.png"), forState: UIControlState.Normal)
You can apply the transform or find a similar image from google
And the background image is here

You set your Title Edge Inset as below. Change its value and set title middle.
floatingButton.titleEdgeInsets = UIEdgeInsetsMake(-24, 0, 0, 0);
according below function change value.
func UIEdgeInsetsMake(top: CGFloat, left: CGFloat, bottom: CGFloat, right: CGFloat) -> UIEdgeInsets

You can simply draw your button with UIBezierPath and here is your code:
import UIKit
class PushButtonView: UIButton {
override func drawRect(rect: CGRect) {
var path = UIBezierPath(ovalInRect: rect)
UIColor.redColor().setFill()
path.fill()
//set up the width and height variables
//for the horizontal stroke
let plusHeight: CGFloat = 3.0
let plusWidth: CGFloat = min(bounds.width, bounds.height) * 0.6
//create the path
var plusPath = UIBezierPath()
//set the path's line width to the height of the stroke
plusPath.lineWidth = plusHeight
//move the initial point of the path
//to the start of the horizontal stroke
plusPath.moveToPoint(CGPoint(x:bounds.width/2 - plusWidth/2 + 0.5, y:bounds.height/2 + 0.5))
//add a point to the path at the end of the stroke
plusPath.addLineToPoint(CGPoint(x:bounds.width/2 + plusWidth/2 + 0.5, y:bounds.height/2 + 0.5))
//Vertical Line
//move to the start of the vertical stroke
plusPath.moveToPoint(CGPoint(x:bounds.width/2 + 0.5, y:bounds.height/2 - plusWidth/2 + 0.5))
//add the end point to the vertical stroke
plusPath.addLineToPoint(CGPoint(x:bounds.width/2 + 0.5, y:bounds.height/2 + plusWidth/2 + 0.5))
//set the stroke color
UIColor.whiteColor().setStroke()
//draw the stroke
plusPath.stroke()
}
}
And your result will be:
You can refer THIS sample project for more Info. And you can modify it as per your need.
Hope It will help you.

Related

How to add a bottom border in swift ios?

I am trying to add a bottom border to my tabs. Here is the code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .systemBackground
self.navigationItem.titleView = UIImageView(
image: UIImage(named: "Twitter")!.resize(25, 25)
)
let leftBarImage = UIImageView(
image: UIImage(named: "Profile")!.resize(35, 35)
)
leftBarImage.layer.borderWidth = 1
leftBarImage.layer.borderColor = UIColor.systemGray4.cgColor
leftBarImage.layer.cornerRadius = leftBarImage.frame.width / 2
leftBarImage.clipsToBounds = true
self.navigationItem.leftBarButtonItem = UIBarButtonItem(
customView: leftBarImage
)
let segmentedControl = UIStackView()
segmentedControl.translatesAutoresizingMaskIntoConstraints = false
segmentedControl.axis = .horizontal
segmentedControl.alignment = .center
segmentedControl.distribution = .fillEqually
segmentedControl.backgroundColor = .green
segmentedControl.addBorder(
for: .Bottom,
color: UIColor.red.cgColor,
thickness: 20
)
for menuLabelText in ["For you", "Following"] {
let menuLabel = UILabel()
menuLabel.text = menuLabelText
menuLabel.textAlignment = .center
menuLabel.font = UIFont.boldSystemFont(ofSize: 16)
segmentedControl.addArrangedSubview(menuLabel)
}
self.view.addSubview(segmentedControl)
NSLayoutConstraint.activate([
segmentedControl.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
segmentedControl.widthAnchor.constraint(equalTo: view.safeAreaLayoutGuide.widthAnchor)
])
}
}
And the border logic,
import UIKit
extension UIView {
enum BorderSide {
case Left, Right, Top, Bottom
}
func addBorder(for side: BorderSide, color: CGColor, thickness: CGFloat) {
let border = CALayer()
border.backgroundColor = color
switch side {
case .Left:
border.frame = CGRect(
x: frame.minX,
y: frame.minY,
width: thickness,
height: frame.height
)
break
case .Right:
border.frame = CGRect(
x: frame.maxX,
y: frame.minY,
width: thickness,
height: frame.height
)
break
case .Top:
border.frame = CGRect(
x: frame.minX,
y: frame.minY,
width: frame.width,
height: thickness
)
break
case .Bottom:
border.frame = CGRect(
x: frame.minX,
y: frame.maxY,
width: frame.width,
height: thickness
)
break
}
layer.addSublayer(border)
}
}
The border is not showing at all. Here is a screenshot:
You can see the green background but not the red border beneath. Any help would be appreciated. Thanks!
First, let's look at what's wrong with the code...
The addBorder(...) func in your UIView extension uses the view's frame -- so, let's put a print() statement right before we try to add the border, to see the the view's frame:
segmentedControl.backgroundColor = .green
// print the frame of segmentedControl to debug console
print("segmentedControl Frame in viewDidLoad():", segmentedControl.frame)
segmentedControl.addBorder(
for: .Bottom,
color: UIColor.red.cgColor,
thickness: 20
)
You will see this in the debug console:
segmentedControl Frame in viewDidLoad(): (0.0, 0.0, 0.0, 0.0)
So your extension tries to set the frame of the layer:
border.frame = CGRect(
x: frame.minX, // minX == 0
y: frame.maxY, // maxY == 0
width: frame.width, // width == 0
height: thickness. // thickness == 20 (passed in call)
)
As we see, we end up with a layer frame of (x: 0.0, y: 0.0, width: 0.0, height: 20.0) ... we won't see anything, because it has no width.
So, let's try adding the border in viewDidLayoutSubviews().
Note that we'll move the segmentedControl creation outside of viewDidLoad() so we can reference it elsewhere. And, we'll leave the addBorder() with red where it was, then call it again with blue in viewDidLayoutSubviews():
class ViewController: UIViewController {
// create it here, so we can reference it outside of viewDidLoad()
let segmentedControl = UIStackView()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .systemBackground
self.title = "Bad Layout"
segmentedControl.translatesAutoresizingMaskIntoConstraints = false
segmentedControl.axis = .horizontal
segmentedControl.alignment = .center
segmentedControl.distribution = .fillEqually
segmentedControl.backgroundColor = .green
// print the frame of segmentedControl to debug console
print("segmentedControl Frame in viewDidLoad():", segmentedControl.frame)
segmentedControl.addBorder(
for: .Bottom,
color: UIColor.red.cgColor,
thickness: 20
)
for menuLabelText in ["For you", "Following"] {
let menuLabel = UILabel()
menuLabel.text = menuLabelText
menuLabel.textAlignment = .center
menuLabel.font = UIFont.boldSystemFont(ofSize: 16)
segmentedControl.addArrangedSubview(menuLabel)
}
self.view.addSubview(segmentedControl)
NSLayoutConstraint.activate([
segmentedControl.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
segmentedControl.widthAnchor.constraint(equalTo: view.safeAreaLayoutGuide.widthAnchor)
])
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// print the frame of segmentedControl to debug console
print("segmentedControl Frame in viewDidLayoutSubviews():", segmentedControl.frame)
segmentedControl.addBorder(
for: .Bottom,
color: UIColor.blue.cgColor,
thickness: 20
)
}
}
extension UIView {
enum BorderSide {
case Left, Right, Top, Bottom
}
func addBorder(for side: BorderSide, color: CGColor, thickness: CGFloat) {
let border = CALayer()
border.backgroundColor = color
switch side {
case .Left:
border.frame = CGRect(
x: frame.minX,
y: frame.minY,
width: thickness,
height: frame.height
)
break
case .Right:
border.frame = CGRect(
x: frame.maxX,
y: frame.minY,
width: thickness,
height: frame.height
)
break
case .Top:
border.frame = CGRect(
x: frame.minX,
y: frame.minY,
width: frame.width,
height: thickness
)
break
case .Bottom:
border.frame = CGRect(
x: frame.minX,
y: frame.maxY,
width: frame.width,
height: thickness
)
break
}
layer.addSublayer(border)
}
}
Now, we see two "print frame" outputs:
segmentedControl Frame in viewDidLoad(): (0.0, 0.0, 0.0, 0.0)
segmentedControl Frame in viewDidLayoutSubviews(): (0.0, 97.66666666666667, 393.0, 19.333333333333332)
Unfortunately, this is the result:
The blue layer is positioned way below the bottom of the stack view / labels.
That's happening because the extension is using frame.maxY -- which is frame.origin.y + frame.size.height -- and the frame's origin.y is 97.66666666666667 (it's top is below the navigation bar).
You could use the same addBorder() approach, by calling it after the views have been laid out (that is, after the frames have been set), and modifying the extension to use the view's bounds instead of frame:
case .Bottom:
border.frame = CGRect(
x: bounds.minX,
y: bounds.maxY,
width: bounds.width,
height: thickness
)
break
and we get this:
However... as should be obvious, this is really not a good approach. Worth noting also is that the layer appears outside the bounds of the view. So, if you were to add a subview constrained to the bottom of segmentedControl, the top of that view would be covered by the 20-point tall "border" layer.
My guess is that you are also going to want to be able to tap the labels... possible you also want to move that "border" to show only under the selected label... etc.
Do some searching / exploring how to subclass UIView so it handles all of that by itself.

Duplicate UILabel inside UIButton

Inside willWillLayoutSubviews() I call UIButton's method setTileTheme(), which I created. Result can be seen below - duplicate UILabel appears under another one. I have already tried calling my method from viewDidLoad() etc., but it didn't help.
Do someone know why I am facing this problem?
func setTileTheme(image: UIImage, title: String) {
self.translatesAutoresizingMaskIntoConstraints = false
tintColor = .green
backgroundColor = .white
setBorder(width: 1.5, color: .lightGray)
roundCorners(radius: 5)
self.layer.masksToBounds = true
let width = self.frame.size.width
let height = self.frame.size.height
let offset: CGFloat = width/4.5
let titleLabel = UILabel(frame: CGRect(x: 0.0, y: 0.0, width: width, height: 30))
titleLabel.center = CGPoint(x: width/2, y: height-offset)
titleLabel.text = title
titleLabel.font = titleLabel.font.withSize(15)
titleLabel.textAlignment = .center
titleLabel.textColor = .darkGray
self.insertSubview(titleLabel, at: 0)
imageEdgeInsets = UIEdgeInsets(top: height/8, left: width/4, bottom: height*3/8, right: width/4)
setImage(image, for: .disabled)
setImage(image.withRenderingMode(.alwaysTemplate), for: .normal)
}
UIButton already has titleLabel and imageView. What you are doing is you are creating a new label and adding it to buttons view which will not replace the default label.
All that you need is
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
return CGRectCGRect(x: 0.0, y: 0.0, width: self.frame.size.width, height: 30)
}
func setTileTheme(image: UIImage, title: String) {
self.translatesAutoresizingMaskIntoConstraints = false
tintColor = .green
backgroundColor = .white
setBorder(width: 1.5, color: .lightGray)
roundCorners(radius: 5)
self.layer.masksToBounds = true
let width = self.frame.size.width
let height = self.frame.size.height
let offset: CGFloat = width/4.5
self.titleLabel?.text = title
self.titleLabel?.font = titleLabel.font.withSize(15)
self.titleLabel?.textAlignment = .center
self.titleLabel?.textColor = .darkGray
imageEdgeInsets = UIEdgeInsets(top: height/8, left: width/4, bottom: height*3/8, right: width/4)
setImage(image, for: .disabled)
setImage(image.withRenderingMode(.alwaysTemplate), for: .normal)
}
I believe you were trying to set the button's title label frame which you can easily set to the default title label of button itself by overriding titleRect
EDIT:
I can see that you are trying to set the inset to Button's image as well. Adding inset to imageView will simply move the image inside imageView but the imageView frame remains the same. Rather in case you wanna affect the imageView's frame itself then you can simply override
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
//whatever calculation you wanna provide
//for example
return CGRect(x: (self.bounds.size.width/2 + 5), y: self.bounds.size.height/2, width: (self.bounds.size.width/2 - 5), height: self.bounds.size.height)
}
Hope it helps

Changing the shape of a UIView border

I am wondering how I would go about implementing a wave-like border of a UIView. Is this possible through UIView's alone? Or would creating this appearance through a UIImageView be the way to go?
An example might be something similar to:
Thanks for your help!
Here is a code based solution that doesn't require any images. This creates a custom view using UIBezierPath to create the sine waves.
import UIKit
class WavyView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
return nil // TODO
}
override func draw(_ rect: CGRect) {
// Fill the whole background with the darkest blue color
UIColor(red: 0.329, green: 0.718, blue: 0.875, alpha: 1).set()
let bg = UIBezierPath(rect: rect)
bg.fill()
// Add the first sine wave filled with a very transparent white
let top1: CGFloat = 17.0
let wave1 = wavyPath(rect: CGRect(x: 0, y: top1, width: frame.width, height: frame.height - top1), periods: 1.5, amplitude: 21, start: 0.55)
UIColor(white: 1.0, alpha: 0.1).set()
wave1.fill()
// Add the second sine wave over the first
let top2: CGFloat = 34.0
let wave2 = wavyPath(rect: CGRect(x: 0, y: top2, width: frame.width, height: frame.height - top2), periods: 1.5, amplitude: 21, start: 0.9)
UIColor(white: 1.0, alpha: 0.15).set()
wave2.fill()
// Add the text
let paraAttrs = NSMutableParagraphStyle()
paraAttrs.alignment = .center
let textRect = CGRect(x: 0, y: frame.maxY - 64, width: frame.width, height: 24)
let textAttrs = [NSFontAttributeName: UIFont.boldSystemFont(ofSize: 20), NSForegroundColorAttributeName: UIColor(white: 1.0, alpha: 0.9), NSParagraphStyleAttributeName: paraAttrs]
("New user? Register here." as NSString).draw(in: textRect, withAttributes: textAttrs)
}
// This creates the desired sine wave bezier path
// rect is the area to fill with the sine wave
// periods is how may sine waves fit across the width of the frame
// amplitude is the height in points of the sine wave
// start is an offset in wavelengths for the left side of the sine wave
func wavyPath(rect: CGRect, periods: Double, amplitude: Double, start: Double) -> UIBezierPath {
let path = UIBezierPath()
// start in the bottom left corner
path.move(to: CGPoint(x: rect.minX, y: rect.maxY))
let radsPerPoint = Double(rect.width) / periods / 2.0 / Double.pi
let radOffset = start * 2 * Double.pi
let xOffset = Double(rect.minX)
let yOffset = Double(rect.minY) + amplitude
// This loops through the width of the frame and calculates and draws each point along the size wave
// Adjust the "by" value as needed. A smaller value gives smoother curve but takes longer to draw. A larger value is quicker but gives a rougher curve.
for x in stride(from: 0, to: Double(rect.width), by: 6) {
let rad = Double(x) / radsPerPoint + radOffset
let y = sin(rad) * amplitude
path.addLine(to: CGPoint(x: x + xOffset, y: y + yOffset))
}
// Add the last point on the sine wave at the right edge
let rad = Double(rect.width) / radsPerPoint + radOffset
let y = sin(rad) * amplitude
path.addLine(to: CGPoint(x: Double(rect.maxX), y: y + yOffset))
// Add line from the end of the sine wave to the bottom right corner
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
// Close the path
path.close()
return path
}
}
// This creates the view with the same size as the image posted in the question
let wavy = WavyView(frame: CGRect(x: 0, y: 0, width: 502, height: 172))
The result of running this code in a Swift playground gives the following:
Obviously you can adjust any of the values in the code above to tweak the result.
The easiest approach would be to use a UIImageView. However, it is also possible by creating a custom border for the UIView but that will require a lot of code to draw the shapes.

How properly calculate cornerRadius depending on the button frame

How to calculate cornerRadius property based on frame of the button to create rounded corners.
I do not like to redefine each time corner cornerRadius for each button item.
I create extension for the UIButton.
extension UIButton {
func setRoundedCorners(){
self.layer.cornerRadius = 10
self.layer.borderWidth = 1
}
}
And i would like to know how too calculate dynamically cornerRadius each time i use this function.
The main issue to find function that will calculate .cornerRadius for different button sizes. Example below will show small difference.
Example:
Corner radius is 10:
:
Corner radius is 15:
Is it possible to find function that will calculate proper value that will give corner radius?
This extension may help you -- it computes and sets the corner radius for a given UIView instance based on its frame property:
extension UIView {
/**
Magically computes and sets an ideal corner radius.
*/
public func magicallySetCornerRadius() {
layer.cornerRadius = 0.188 * min(frame.width, frame.height)
layer.masksToBounds = true
}
}
Here's how I've used it in a "ViewController.swift" file for three differently sized UIButtons:
import UIKit
class ViewController: UIViewController {
private func addButton(frame: CGRect, backgroundColor: UIColor) {
let button = UIButton(frame: frame)
button.backgroundColor = backgroundColor
button.magicallySetCornerRadius()
button.layer.borderColor = UIColor.black.cgColor
button.layer.borderWidth = 1.0
view.addSubview(button)
}
override func viewDidLoad() {
super.viewDidLoad()
addButton(frame: CGRect(x: 40.0, y: 100.0, width: 100.0, height: 300.0), backgroundColor: .blue)
addButton(frame: CGRect(x: 160.0, y: 180.0, width: 100.0, height: 100.0), backgroundColor: .green)
addButton(frame: CGRect(x: 160.0, y: 300.0, width: 180.0, height: 100.0), backgroundColor: .red)
addButton(frame: CGRect(x: 40.0, y: 420.0, width: 300.0, height: 150.0), backgroundColor: .yellow)
}
}
... and this is what the result looks like:
Try this code:
btn1, btn2 and btnarrival are UIButton instances:
btn1.setCornerRadius()
btn2.setCornerRadius()
btnarrival.setCornerRadius()
extension UIButton {
func setCornerRadius() {
layer.cornerRadius = frame.size.height / 2.0
clipsToBounds = true
}
}
The main idea to get flexible function that will calculate proper
corner radius for different buttons based on their frame
I think it's up to you what you pass as the arguments:
extension UIButton {
func setRoundedCorners(ratio: Double?) {
if let r = ratio {
self.layer.cornerRadius = self.frame.size.width*r // for specific corner ratio
} else {
// circle
// i would put a condition, as width and height differ:
if(self.frame.size.width == self.frame.size.height) {
self.layer.cornerRadius = self.frame.size.width/2 // for circles
} else {
//
}
}
self.layer.borderWidth = 1
}
}
Usage
let button = UIButton()
button.setRoundedCorners(ratio: 0.25) // shape with rounded corners
button.setRoundedCorners() // circle
I was looking for the same thing and found myself a very clear and easy solution. As it is an extension to UIView you can not only use it on buttons but several objects.
extension UIView{
func cornerCalculation(r: CGFloat) {
self.layer.cornerRadius = self.frame.height/2 * r
}
}
and then just call:
button.cornerCalculation(r: 1)
r would be any number between 0 (sharp edges) and 1 (completley rounded edges)

How to set cornerRadius to UIImage inside a Button?

I am creating Image with gradient, and i want the button to have cornerRadius
button = UIButton.buttonWithType(UIButtonType.Custom) as! UIButton
button.frame = CGRectMake(self.view.frame.size.width/2 - button.frame.size.width, 100, 250, 50)
button.layer.cornerRadius = 3
button.setTitle("ViewThree", forState: UIControlState.Normal)
button.addTarget(self, action: "ViewControllerAction:", forControlEvents: UIControlEvents.TouchUpInside)
button.setBackgroundImage(getImageWithGradient(UIColor(netHex:0x2d72cf).CGColor, bottom: UIColor(netHex:0x2d72cf).CGColor, size: CGSize(width: button.bounds.width, height: button.bounds.height), frame: button.bounds), forState: UIControlState.Normal)
func getImageWithGradient(top: CGColor, bottom: CGColor, size: CGSize, frame: CGRect) -> UIImage{
var image = UIImage()
UIGraphicsBeginImageContext(frame.size)
var context = UIGraphicsGetCurrentContext()
image.drawAtPoint(CGPointMake(0, 0))
let colorSpace = CGColorSpaceCreateDeviceRGB()
let locations:[CGFloat] = [0.0, 1.0]
let gradient = CGGradientCreateWithColors(colorSpace,
[top, bottom], locations)
let startPoint = CGPointMake(frame.size.width / 2, 0)
let endPoint = CGPointMake(frame.size.width / 2, frame.size.height)
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0)
image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
but no matter what i try there is no corner radius, how should i try this
Try setting the UIButton's clipToBounds property to YES
Go to your story board and click on the button. Go to this buttons identity inspector and "User Defined Runtime Attributes" for Key Path "layer.cornerRadius" for Type "Number" for Value "10" i like to use 10 but you can play around with it! If you are doing this in code, under your line
button.layer.cornerRadius = 3
add the line
button.layer.masksToBounds = true

Resources