How to add border to the TOP of selected TabBar.
Below method I have used to add it at bottom but I want
extension UIImage {
func createSelectionIndicator(color: UIColor, size: CGSize, lineWidth: CGFloat) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, false, 0)
color.setFill()
UIRectFill(CGRect(x: 0, y: size.height - lineWidth, width: size.width, height: lineWidth))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
tabBar.selectionIndicatorImage = UIImage().createSelectionIndicator(color: BLUE, size: CGSize(width: tabBar.frame.width/CGFloat(tabBar.items!.count), height: tabBar.frame.height), lineWidth: 2.0)
You can use the function to create borders on any side -
enum Side: String {
case top, left, right, bottom
}
extension UIImage {
func createSelectionIndicator(color: UIColor, size: CGSize, lineThickness: CGFloat, side: Side) -> UIImage {
var xPosition = 0.0
var yPosition = 0.0
var imgWidth = 2.0
var imgHeight = 2.0
switch side {
case .top:
xPosition = 0.0
yPosition = 0.0
imgWidth = size.width
imgHeight = lineThickness
case .bottom:
xPosition = 0.0
yPosition = size.height - lineThickness
imgWidth = size.width
imgHeight = lineThickness
case .left:
xPosition = 0.0
yPosition = 0.0
imgWidth = lineThickness
imgHeight = size.height
case .right:
xPosition = size.width - lineThickness
yPosition = 0.0
imgWidth = lineThickness
imgHeight = size.height
}
UIGraphicsBeginImageContextWithOptions(size, false, 0)
color.setFill()
UIRectFill(CGRect(x: xPosition, y: yPosition, width: imgWidth, height: imgHeight))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
tabBar.selectionIndicatorImage = UIImage().createSelectionIndicator(color: BLUE, size: CGSize(width: tabBar.frame.width/CGFloat(tabBar.items!.count), height: tabBar.frame.height), lineThickness: 2.0, side: .top)
Let me know if this was useful or you need any other help.
Happy Coding :)
import UIKit
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
tabBarController?.tabBar.addBorder(.top, color: .red, thickness: 2.0)
}
}
extension UITabBar {
func addBorder(_ edge: UIRectEdge, color: UIColor, thickness: CGFloat) {
let subview = UIView()
subview.translatesAutoresizingMaskIntoConstraints = false
subview.backgroundColor = color
self.addSubview(subview)
switch edge {
case .top, .bottom:
subview.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 0).isActive = true
subview.rightAnchor.constraint(equalTo: self.rightAnchor, constant: 0).isActive = true
subview.heightAnchor.constraint(equalToConstant: thickness).isActive = true
if edge == .top {
subview.topAnchor.constraint(equalTo: self.topAnchor, constant: 0).isActive = true
} else {
subview.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true
}
case .left, .right:
subview.topAnchor.constraint(equalTo: self.topAnchor, constant: 0).isActive = true
subview.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true
subview.widthAnchor.constraint(equalToConstant: thickness).isActive = true
if edge == .left {
subview.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 0).isActive = true
} else {
subview.rightAnchor.constraint(equalTo: self.rightAnchor, constant: 0).isActive = true
}
default:
break
}
}
}
tabBarController?.tabBar.addBorder(.top, color: .orange, thickness: 5.0)
Related
Due to the auto layout feature, I tried to use UIImage corner radius instead of UIImageView. The corner radius was too thin when the photo was too large, such as 4k x 4k, but when the photo was small, such as 500 x 500, the corner radius was too large. No matter what size the photo is, I want the corner radius to be 25. Do you have any suggestions?
I tried the following code from this, but it does not solve my problem.:
https://newbedev.com/how-to-set-corner-radius-to-uiimage-not-uiimageview-in-ios-swift
My goal is to have the corner radius equal to the size of any photo. I tested the image name "demo" which is 4,000 x 4,000, the corner radius is like 5, and "demo2" which is 500 x 500, the corner radius is like 50.
Here my full code:
class TheCountdownDetails: UIViewController {
let photoPreview = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
photoPreview.translatesAutoresizingMaskIntoConstraints = false
photoPreview.contentMode = .scaleAspectFit
view.addSubview(photoPreview)
photoPreview.topAnchor.constraint(equalTo: view.topAnchor, constant: 20).isActive = true
photoPreview.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
photoPreview.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20).isActive = true
photoPreview.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20).isActive = true
}
override func viewDidLayoutSubviews() {
photoPreview.image = UIImage(named: "demo")?.withRoundedCorners(radius: 25)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
}
}
extension UIImage {
public func withRoundedCorners(radius: CGFloat? = nil) -> UIImage? {
let maxRadius = min(size.width, size.height) / 2
let cornerRadius: CGFloat
if let radius = radius, radius > 0 && radius <= maxRadius {
cornerRadius = radius
} else {
cornerRadius = maxRadius
}
UIGraphicsBeginImageContextWithOptions(size, false, scale)
let rect = CGRect(origin: .zero, size: size)
UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).addClip()
draw(in: rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
If you use debug to inspect the UIImage returned from your withRoundedCorners(...) func, you'll see that both images do, in fact, have the same rounded corners.
The problem is that you are using a radius of 25 on a 4k x 4k image, and a radius of 25 on a 500 x 500 image, but then scaling them to fit your imageView.
If you change your imageView's content mode to:
photoPreview.contentMode = .topLeft
the images won't scale, and you'll see that you're getting the same radius rounded corners.
So, you need to scale the image at the same time you're clipping the rounded corners.
Here's a modification of your extension:
extension UIImage {
func withRoundedCorners(radius: CGFloat? = nil, targetSize: CGSize) -> UIImage {
// First, determine the scale factor that preserves aspect ratio
let widthRatio = targetSize.width / size.width
let heightRatio = targetSize.height / size.height
let scaleFactor = min(widthRatio, heightRatio)
// Compute the new image size that preserves aspect ratio
let scaledImageSize = CGSize(
width: size.width * scaleFactor,
height: size.height * scaleFactor
)
let maxRadius = min(scaledImageSize.width, scaledImageSize.height) / 2
let cornerRadius: CGFloat
if let radius = radius, radius > 0 && radius <= maxRadius {
cornerRadius = radius
} else {
cornerRadius = maxRadius
}
let newRect: CGRect = CGRect(origin: .zero, size: scaledImageSize)
let renderer = UIGraphicsImageRenderer(size: newRect.size)
let scaledImage = renderer.image { _ in
UIBezierPath(roundedRect: newRect, cornerRadius: cornerRadius).addClip()
self.draw(in: newRect)
}
return scaledImage
}
}
and an example controller, putting two imageViews in a stack view, so we can see two different size images at the same time:
class TheCountdownDetails: UIViewController {
let photoPreview1 = UIImageView()
let photoPreview2 = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
let stack = UIStackView()
stack.axis = .vertical
stack.distribution = .fillEqually
stack.spacing = 20
stack.translatesAutoresizingMaskIntoConstraints = false
stack.addArrangedSubview(photoPreview1)
stack.addArrangedSubview(photoPreview2)
view.addSubview(stack)
photoPreview1.contentMode = .center
photoPreview2.contentMode = .center
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
stack.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
stack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
stack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
stack.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
])
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// image views are in a stack view,
// so we need to force their layouts
// before asking for their frames
photoPreview1.setNeedsLayout()
photoPreview1.layoutIfNeeded()
photoPreview2.setNeedsLayout()
photoPreview2.layoutIfNeeded()
guard let img1 = UIImage(named: "image4kx4k") else { return }
guard let img2 = UIImage(named: "image500x500") else { return }
let img1r = img1.withRoundedCorners(radius: 25, targetSize: photoPreview1.frame.size)
let img2r = img2.withRoundedCorners(radius: 25, targetSize: photoPreview2.frame.size)
photoPreview1.image = img1r
photoPreview2.image = img2r
}
}
Using this 4kx4k image (original source: https://images.wallpaperscraft.com/image/single/night_city_aerial_view_city_lights_130879_4000x4000.jpg):
and this 500x500 image (original source: https://www.digitalphotopix.com/wp-content/uploads/2011/02/blue-lake.jpg)
We get this output:
How to add a shadow effect to this gradient border.
Here is the sample extension to create a border layer with a specified width. When I tried to add a shadow layer whole UI gets affected.
self.gradientBorder(width: 3, colors: UIColor.defaultGradient, andRoundCornersWithRadius: min(bounds.size.height, bounds.size.width))
extension UIView {
private static let kLayerNameGradientBorder = "GradientBorderLayer"
func gradientBorder(width: CGFloat,
colors: [UIColor],
startPoint: CGPoint = CGPoint(x: 1.0, y: 0.0),
endPoint: CGPoint = CGPoint(x: 1.0, y: 1.0),
andRoundCornersWithRadius cornerRadius: CGFloat = 0) {
let existingBorder = gradientBorderLayer()
let border = existingBorder ?? CAGradientLayer()
border.frame = CGRect(x: bounds.origin.x, y: bounds.origin.y,
width: bounds.size.width + width, height: bounds.size.height + width)
border.colors = colors.map { $0.cgColor }
border.startPoint = startPoint
border.endPoint = endPoint
let mask = CAShapeLayer()
let maskRect = CGRect(x: bounds.origin.x + width/2, y: bounds.origin.y + width/2,
width: bounds.size.width - width, height: bounds.size.height - width)
let path = UIBezierPath(roundedRect: maskRect, cornerRadius: cornerRadius).cgPath
mask.path = path
mask.fillColor = UIColor.clear.cgColor
mask.strokeColor = UIColor.black.cgColor
mask.backgroundColor = UIColor.black.cgColor
mask.lineWidth = width
mask.masksToBounds = false
border.mask = mask
let exists = (existingBorder != nil)
if !exists {
layer.addSublayer(border)
}
}
private func gradientBorderLayer() -> CAGradientLayer? {
let borderLayers = layer.sublayers?.filter { return $0.name == UIView.kLayerNameGradientBorder }
if borderLayers?.count ?? 0 > 1 {
fatalError()
}
return borderLayers?.first as? CAGradientLayer
}
}
Edit
Minor changes from initial code:
background layer doesn't interfere with added subviews
handles resizing correctly (when called in viewDidLayoutSubviews)
You can do this by adding a shadow properties to the view's layer, and adding another layer as a "background" layer.
After Edit... Here is your UIView extension - slightly modified (see the comments):
extension UIView {
private static let kLayerNameGradientBorder = "GradientBorderLayer"
private static let kLayerNameBackgroundLayer = "BackgroundLayer"
func gradientBorder(width: CGFloat,
colors: [UIColor],
startPoint: CGPoint = CGPoint(x: 1.0, y: 0.0),
endPoint: CGPoint = CGPoint(x: 1.0, y: 1.0),
andRoundCornersWithRadius cornerRadius: CGFloat = 0,
bgColor: UIColor = .white,
shadowColor: UIColor = .black,
shadowRadius: CGFloat = 5.0,
shadowOpacity: Float = 0.75,
shadowOffset: CGSize = CGSize(width: 0.0, height: 0.0)
) {
let existingBackground = backgroundLayer()
let bgLayer = existingBackground ?? CALayer()
bgLayer.name = UIView.kLayerNameBackgroundLayer
// set its color
bgLayer.backgroundColor = bgColor.cgColor
// insert at 0 to not cover other layers
if existingBackground == nil {
layer.insertSublayer(bgLayer, at: 0)
}
// use same cornerRadius as border
bgLayer.cornerRadius = cornerRadius
// inset its frame by 1/2 the border width
bgLayer.frame = bounds.insetBy(dx: width * 0.5, dy: width * 0.5)
// set shadow properties
layer.shadowColor = shadowColor.cgColor
layer.shadowRadius = shadowRadius
layer.shadowOpacity = shadowOpacity
layer.shadowOffset = shadowOffset
let existingBorder = gradientBorderLayer()
let border = existingBorder ?? CAGradientLayer()
border.name = UIView.kLayerNameGradientBorder
// don't do this
// border.frame = CGRect(x: bounds.origin.x, y: bounds.origin.y,
// width: bounds.size.width + width, height: bounds.size.height + width)
// use this instead
border.frame = bounds
border.colors = colors.map { $0.cgColor }
border.startPoint = startPoint
border.endPoint = endPoint
let mask = CAShapeLayer()
let maskRect = CGRect(x: bounds.origin.x + width/2, y: bounds.origin.y + width/2,
width: bounds.size.width - width, height: bounds.size.height - width)
let path = UIBezierPath(roundedRect: maskRect, cornerRadius: cornerRadius).cgPath
mask.path = path
mask.fillColor = UIColor.clear.cgColor
mask.strokeColor = UIColor.black.cgColor
mask.backgroundColor = UIColor.black.cgColor
mask.lineWidth = width
mask.masksToBounds = false
border.mask = mask
let exists = (existingBorder != nil)
if !exists {
layer.addSublayer(border)
}
}
private func backgroundLayer() -> CALayer? {
let aLayers = layer.sublayers?.filter { return $0.name == UIView.kLayerNameBackgroundLayer }
if aLayers?.count ?? 0 > 1 {
fatalError()
}
return aLayers?.first
}
private func gradientBorderLayer() -> CAGradientLayer? {
let borderLayers = layer.sublayers?.filter { return $0.name == UIView.kLayerNameGradientBorder }
if borderLayers?.count ?? 0 > 1 {
fatalError()
}
return borderLayers?.first as? CAGradientLayer
}
}
After Edit... and here's an example in-use:
class GradBorderViewController: UIViewController {
var topGradView: UIView = UIView()
// make bottom grad view a button
var botGradView: UIButton = UIButton()
var topBkgView: UIView = UIView()
var botBkgView: UIView = UIView()
let embededLabel: UILabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
embededLabel.textColor = .red
embededLabel.textAlignment = .center
embededLabel.text = "Label as subview"
botGradView.setTitle("Button", for: [])
botGradView.setTitleColor(.red, for: [])
botGradView.setTitleColor(.lightGray, for: .highlighted)
topGradView.backgroundColor = .clear
botGradView.backgroundColor = .clear
topBkgView.backgroundColor = .yellow
botBkgView.backgroundColor = UIColor(red: 0.5, green: 0.0, blue: 0.0, alpha: 1.0)
[topBkgView, topGradView, botBkgView, botGradView].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
}
embededLabel.translatesAutoresizingMaskIntoConstraints = false
// embed label in topGradView
topGradView.addSubview(embededLabel)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// yellow background view on top half, dark-red background view on bottom half
topBkgView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
topBkgView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
botBkgView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
botBkgView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
topBkgView.topAnchor.constraint(equalTo: g.topAnchor),
botBkgView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
topBkgView.heightAnchor.constraint(equalTo: g.heightAnchor, multiplier: 0.5),
botBkgView.topAnchor.constraint(equalTo: topBkgView.bottomAnchor),
// each grad border view 75% of width, 80-pt constant height
topGradView.widthAnchor.constraint(equalTo: topBkgView.widthAnchor, multiplier: 0.75),
topGradView.heightAnchor.constraint(equalToConstant: 80.0),
botGradView.widthAnchor.constraint(equalTo: topGradView.widthAnchor),
botGradView.heightAnchor.constraint(equalTo: topGradView.heightAnchor),
// center each grad border view in a background view
topGradView.centerXAnchor.constraint(equalTo: topBkgView.centerXAnchor),
topGradView.centerYAnchor.constraint(equalTo: topBkgView.centerYAnchor),
botGradView.centerXAnchor.constraint(equalTo: botBkgView.centerXAnchor),
botGradView.centerYAnchor.constraint(equalTo: botBkgView.centerYAnchor),
// center the embedded label in the topGradView
embededLabel.centerXAnchor.constraint(equalTo: topGradView.centerXAnchor),
embededLabel.centerYAnchor.constraint(equalTo: topGradView.centerYAnchor),
])
botGradView.addTarget(self, action: #selector(self.testTap(_:)), for: .touchUpInside)
}
#objc func testTap(_ sender: Any?) -> Void {
print("Tapped!")
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let a1: [CGFloat] = [173, 97, 222].map({$0 / 255.0})
let a2: [CGFloat] = [0, 198, 182].map({$0 / 255.0})
let c1 = UIColor(red: a1[0], green: a1[1], blue: a1[2], alpha: 1.0)
let c2 = UIColor(red: a2[0], green: a2[1], blue: a2[2], alpha: 1.0)
topGradView.gradientBorder(width: 6,
colors: [c1, c2],
startPoint: CGPoint(x: 0.0, y: 0.0),
endPoint: CGPoint(x: 1.0, y: 1.0),
andRoundCornersWithRadius: topGradView.frame.height * 0.5
)
botGradView.gradientBorder(width: 6,
colors: [c1, c2],
startPoint: CGPoint(x: 0.0, y: 0.0),
endPoint: CGPoint(x: 1.0, y: 1.0),
andRoundCornersWithRadius: topGradView.frame.height * 0.5,
shadowColor: .white,
shadowRadius: 12,
shadowOpacity: 0.95,
shadowOffset: CGSize(width: 0.0, height: 0.0)
)
}
}
After Edit... Results:
I have created UIView and it has one subview and it's containing shape object which are creating using UIBezierPath, and UIView has fixed height and width (image 1). When i click the blue color button, it should be scale down to another fixed width and height. I have applied CGAffineTransform to subview and, i guess it is scale down properly, but since topAnchor and leftAchor has constant values, after scale down it is not displaying properly (image 2).I will attach my source code here and screenshots, please, analyze this and can someone suggest better approach for how to do this ? and highly appreciate ur feedback and comments.
code:
import UIKit
class ViewController: UIViewController {
var screenView: UIView!
var button: UIButton!
let widthOfScaleDownView: CGFloat = 200
let heightOfScaleDownView: CGFloat = 300
override func viewDidLoad() {
screenView = UIView(frame: .zero)
screenView.translatesAutoresizingMaskIntoConstraints = false
screenView.backgroundColor = UIColor.red
self.view.addSubview(screenView)
NSLayoutConstraint.activate([
screenView.heightAnchor.constraint(equalToConstant: 800),
screenView.widthAnchor.constraint(equalToConstant: 600),
screenView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: 0),
screenView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor, constant: 0)
])
button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = UIColor.blue
self.view.addSubview(button)
NSLayoutConstraint.activate([
button.heightAnchor.constraint(equalToConstant: 50),
button.widthAnchor.constraint(equalToConstant: 100),
button.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 950),
button.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 350)
])
button.setTitle("click", for: .normal)
button.setTitleColor(UIColor.white, for: .normal)
button.addTarget(self, action: #selector(scaleDownView(_:)), for: .touchUpInside)
let shapeView = UIView(frame: .zero)
shapeView.translatesAutoresizingMaskIntoConstraints = false
let rect = CGRect(x: 0, y: 0, width: 300, height: 300)
let shape = UIBezierPath(roundedRect: rect, cornerRadius: 50)
let layer = CAShapeLayer()
layer.path = shape.cgPath
layer.lineWidth = 2
shapeView.layer.addSublayer(layer)
screenView.addSubview(shapeView)
NSLayoutConstraint.activate([
shapeView.topAnchor.constraint(equalTo: screenView.topAnchor, constant: 250),
shapeView.leftAnchor.constraint(equalTo: screenView.leftAnchor, constant: 150)
])
}
#IBAction func scaleDownView(_ sender: UIButton) {
let widthScale = widthOfScaleDownView/screenView.frame.width
let heightScale = heightOfScaleDownView/screenView.frame.height
screenView.subviews.forEach{ view in
view.transform = CGAffineTransform(scaleX: widthScale, y: heightScale)
}
NSLayoutConstraint.activate([
screenView.heightAnchor.constraint(equalToConstant: heightOfScaleDownView),
screenView.widthAnchor.constraint(equalToConstant: widthOfScaleDownView),
screenView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: 0),
screenView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor, constant: 0)
])
}
}
Couple things...
1 - When you use CGAffineTransform to change a view, it will automatically affect the view's subviews. So no need for a loop.
2 - Transforms are cumulative, so when "going back" to the original scale, use .identity instead of another scale transform.
3 - You cannot set a constraint multiple times. So if you do:
screenView.heightAnchor.constraint(equalToConstant: 800)
and then you also do:
screenView.heightAnchor.constraint(equalToConstant: heightOfScaleDownView)
you will get auto-layout conflicts... the view cannot be both 800-pts tall and
300-pts tall at the same time.
Take a look at the changes I made to your code. I also added a 1-second animation to the scale transform so you can see what it's doing:
class ViewController: UIViewController {
var screenView: UIView!
var button: UIButton!
let widthOfScaleDownView: CGFloat = 200
let heightOfScaleDownView: CGFloat = 300
override func viewDidLoad() {
screenView = UIView(frame: .zero)
screenView.translatesAutoresizingMaskIntoConstraints = false
screenView.backgroundColor = UIColor.red
self.view.addSubview(screenView)
// constrain screenView
// width: 800 height: 600
// centered X and Y
NSLayoutConstraint.activate([
screenView.heightAnchor.constraint(equalToConstant: 800),
screenView.widthAnchor.constraint(equalToConstant: 600),
screenView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: 0),
screenView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor, constant: 0)
])
button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = UIColor.blue
self.view.addSubview(button)
NSLayoutConstraint.activate([
button.heightAnchor.constraint(equalToConstant: 50),
button.widthAnchor.constraint(equalToConstant: 100),
button.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 950),
button.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 350)
])
button.setTitle("click", for: .normal)
button.setTitleColor(UIColor.white, for: .normal)
button.addTarget(self, action: #selector(scaleDownView(_:)), for: .touchUpInside)
let shapeView = UIView(frame: .zero)
shapeView.translatesAutoresizingMaskIntoConstraints = false
let rect = CGRect(x: 0, y: 0, width: 300, height: 300)
let shape = UIBezierPath(roundedRect: rect, cornerRadius: 50)
let layer = CAShapeLayer()
layer.path = shape.cgPath
layer.lineWidth = 2
shapeView.layer.addSublayer(layer)
screenView.addSubview(shapeView)
// constrain shapeView
// width: 300 height: 300
// centered X and Y in screenView
NSLayoutConstraint.activate([
shapeView.widthAnchor.constraint(equalToConstant: 300.0),
shapeView.heightAnchor.constraint(equalToConstant: 300.0),
shapeView.centerXAnchor.constraint(equalTo: screenView.centerXAnchor),
shapeView.centerYAnchor.constraint(equalTo: screenView.centerYAnchor),
])
}
#IBAction func scaleDownView(_ sender: UIButton) {
let widthScale = widthOfScaleDownView/screenView.frame.width
let heightScale = heightOfScaleDownView/screenView.frame.height
UIView.animate(withDuration: 1.0) {
// if we have already been scaled down
if widthScale == 1 {
// animate the scale transform back to original (don't add another transform)
self.screenView.transform = .identity
} else {
// animate the scale-down transform
self.screenView.transform = CGAffineTransform(scaleX: widthScale, y: heightScale)
}
}
// do NOT change screenView constraints!
}
}
I’m trying to put a gradient on a button, but it is not showing up. All the other button parts are working and displaying perfectly!
Function that creates the button
private func createButtonMenu(buttonTitle: String, buttonIcon: UIImage, colorOne: UIColor, colorTwo: UIColor) -> UIButton{
let button = UIButton()
button.setTitle(buttonTitle, for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.contentHorizontalAlignment = .leading
button.contentVerticalAlignment = .bottom
button.contentEdgeInsets = UIEdgeInsets(top: 0, left: 10, bottom: 10, right: 0)
button.layer.cornerRadius = 30
button.layer.masksToBounds = true
let icon = UIImageView(image: buttonIcon)
icon.translatesAutoresizingMaskIntoConstraints = false
button.addSubview(icon)
button.heightAnchor.constraint(equalToConstant: 100).isActive = true
icon.topAnchor.constraint(equalTo: button.topAnchor, constant: 10).isActive = true
icon.leadingAnchor.constraint(equalTo: button.leadingAnchor, constant: 10).isActive = true
let gradientLayer = CAGradientLayer()
gradientLayer.frame = button.bounds
gradientLayer.colors = [UIColor.red, UIColor.blue]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
button.layer.insertSublayer(gradientLayer, at: 0)
//button.backgroundColor = .red
icon.widthAnchor.constraint(equalToConstant: 30).isActive = true
icon.heightAnchor.constraint(equalToConstant: 30).isActive = true
return button
}
Function that inserts the button in view
private func configuringButtons(){
//Creating buttons
let userButtonMenu: UIButton = createButtonMenu(buttonTitle: "009", buttonIcon: #imageLiteral(resourceName: "woman"), colorOne: .red, colorTwo: .blue)
view.addSubview(userButtonMenu)
}
Colours in a gradient layer must be of type CGColor, not UIColor.
gradientLayer.colors = [UIColor.red.cgColor, UIColor.blue.cgColor]
I created extension for the view's constraints. You can find the code in the below but after new release iPhone X we need to use safeAreaInsets but i don't know how implement that property. I'll be so glad if you could help me out.
Thanks
extension UIView {
func anchor (top: NSLayoutYAxisAnchor?, left: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, right: NSLayoutXAxisAnchor?, paddingTop: CGFloat, paddingLeft: CGFloat, paddingBottom: CGFloat, paddingRight: CGFloat, width: CGFloat, height: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
self.topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
}
if let left = left {
self.leftAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true
}
if let right = right {
rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true
}
if height != 0 {
heightAnchor.constraint(equalToConstant: height).isActive = true
}
if width != 0 {
widthAnchor.constraint(equalToConstant: width).isActive = true
}
}
}
I have refactored your code to take into account the insets. Try the following:
import UIKit
extension UIView {
func anchor (top: NSLayoutYAxisAnchor?, left: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, right: NSLayoutXAxisAnchor?, paddingTop: CGFloat, paddingLeft: CGFloat, paddingBottom: CGFloat, paddingRight: CGFloat, width: CGFloat, height: CGFloat, enableInsets: Bool) {
var topInset = CGFloat(0)
var bottomInset = CGFloat(0)
var rightInset = CGFloat(0)
var leftInset = CGFloat(0)
if #available(iOS 11, *), enableInsets {
let insets = self.safeAreaInsets
topInset = insets.top
bottomInset = insets.bottom
rightInset = insets.right
leftInset = insets.left
}
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
self.topAnchor.constraint(equalTo: top, constant: paddingTop+topInset).isActive = true
}
if let left = left {
self.leftAnchor.constraint(equalTo: left, constant: paddingLeft+leftInset).isActive = true
}
if let right = right {
rightAnchor.constraint(equalTo: right, constant: -paddingRight-rightInset).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom-bottomInset).isActive = true
}
if height != 0 {
heightAnchor.constraint(equalToConstant: height).isActive = true
}
if width != 0 {
widthAnchor.constraint(equalToConstant: width).isActive = true
}
}
}