Creating a UIbutton from an already defined Bezier Path? - ios

I have a range of defined Bezier Paths within one View Controller (the default). However specifically, I want to make one of them a UIButton (It doesn't have to lead anywhere yet but it would be great if it could print something on touch).
By looking at some similar questions, I have been able to define the Bezier Path I want and the UIButton separately on the simulator, but haven't been able to splice them together.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let circlePath = UIBezierPath(arcCenter: CGPoint(x: 200, y: 350), radius: CGFloat(150), startAngle: CGFloat(0), endAngle:CGFloat(Double.pi * 2), clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
//change the fill color
shapeLayer.fillColor = UIColor.clear.cgColor
//you can change the stroke color
shapeLayer.strokeColor = UIColor.gray.cgColor
//you can change the line width
shapeLayer.lineWidth = 7.5
view.layer.addSublayer(shapeLayer)
let button = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
button.backgroundColor = .green
button.setTitle("Test Button", for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
self.view.addSubview(button)
}
#objc func buttonAction(sender: UIButton!) {
print("Button tapped")
}
}
How can I pass circlePath as a UIButton?.

A UIButton is a UIView so you can add the layer you created to the button in just the same way as you did the view:
button.layer.addSublayer(shapeLayer)
Note that this layer will then cover the label of the UIButton, but an easy way to solve this is to change the z-position of the layer:
shapeLayer.zPosition = -1
E.g. if you want to add a circular layer to a button:
let circlePath = UIBezierPath(ovalIn: button.bounds)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
shapeLayer.zPosition = -1
button.layer.addSublayer(shapeLayer)
Or, matching the circle in your example but creating the button before the shape:
// define circle parameters
let radius: CGFloat = 150
let center = CGPoint(x: 200, y: 350)
// create the button
let button = UIButton(frame: CGRect(origin: center.applying(CGAffineTransform(translationX: -radius, y: -radius)),
size: CGSize(width: 2 * radius, height: 2 * radius)))
// create the circle layer
let circlePath = UIBezierPath(ovalIn: button.bounds)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
shapeLayer.zPosition = -1
// add the circle layer to the button
button.layer.addSublayer(shapeLayer)
view.addSubview(button)

Related

How to have a UIView with line corners instead of rounded corners in the bottom

I want to create a UIView with this shape that has line corners. How can I draw that with UIBezierPath or is there an easier way to do so? Any help would be very appreciated!
Yes, you should probably create a path that draws that shape.
Probably the most processor-efficient way to do that would be to install a CAShapeLayer into the view as a sublayer, and install a path into the shape layer's path property. Consider the following playground:
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
let shapeLayer = CAShapeLayer()
let dogEarValue: CGFloat = 40.0
func buildShape() {
let box = view.bounds
shapeLayer.frame = box
let path = UIBezierPath()
path.move(to: box.origin)
path.addLine(to: CGPoint(x:box.maxX, y: box.origin.y))
path.addLine(to: CGPoint(x:box.maxX, y: box.maxY - dogEarValue))
path.addLine(to: CGPoint(x:box.maxX - dogEarValue, y: box.maxY))
path.addLine(to: CGPoint(x:box.origin.x + dogEarValue, y: box.maxY))
path.addLine(to: CGPoint(x:box.origin.x, y: box.maxY - dogEarValue))
path.close()
shapeLayer.path = path.cgPath
}
override func loadView() {
let view = UIView()
view.backgroundColor = .white
shapeLayer.fillColor = UIColor.black.cgColor
shapeLayer.backgroundColor = UIColor.clear.cgColor
view.layer.addSublayer(shapeLayer)
let label = UILabel()
label.text = "Hello World"
label.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
label.text = "Hello World!"
label.textColor = .black
view.addSubview(label)
self.view = view
}
override func viewDidLayoutSubviews() {
buildShape()
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()

how to programmatically show a colored disk behind a UIImageView, in swift on an iOS device

I have an icon that I programmatically display.
How can I display a solid, colored disk behind the icon, at the same screen position?
Icon is opaque.
I will sometimes programmatically change screen position and disk's diameter and color.
Here's how I programmatically display the icon now.............
DispatchQueue.main.async
{
ViewController.wrist_band_UIImageView.frame = CGRect( x: screen_position_x,
y: screen_position_y,
width: App_class.screen_width,
height: App_class.screen_height)
}
UPDATE #1 for D.Mika below...
UPDATE #2 for D.Mika below...
import UIKit
import Foundation
class ViewController: UIViewController
{
static var circleView: UIView!
static let wrist_band_UIImageView: UIImageView = {
let theImageView = UIImageView()
theImageView.image = UIImage( systemName: "applewatch" )
theImageView.translatesAutoresizingMaskIntoConstraints = false
return theImageView
}()
override func viewDidLoad()
{
super.viewDidLoad()
view.addSubview( ViewController.wrist_band_UIImageView )
// Init disk image:
ViewController.circleView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0))
ViewController.circleView.backgroundColor = .red
ViewController.circleView.layer.cornerRadius = view.bounds.size.height / 2.0
ViewController.circleView.clipsToBounds = true
// Add view to view hierarchy below image view
ViewController.circleView.insertSubview(view, belowSubview: ViewController.wrist_band_UIImageView)
App_class.display_single_wearable()
}
}
class App_class
{
static var is_communication_established = false
static var total_packets = 0
static func display_single_wearable()
{
ViewController.wrist_band_UIImageView.frame = CGRect(x: 0, y: 0,
width: 100, height: 100)
ViewController.circleView.frame = CGRect( x: 0,
y: 0,
width: 100,
height: 100)
ViewController.circleView.layer.cornerRadius = 100
}
static func process_location_xy(text_location_xyz: String)
{}
} // App_class
You can display a colored circle using the following view:
circleView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0))
circleView.backgroundColor = .red
circleView.layer.cornerRadius = view.bounds.size.height / 2.0
circleView.clipsToBounds = true
// Add view to view hierarchy below image view
view.insertSubview(circleView, belowSubview: wrist_band_UIImageView)
You can change it's position and size by applying a new frame. But you have to change the layer's cornerRadius accordingly.
This is a simple example in a playground:
Edit:
I've modified the sample code to add the view the view hierarchy.
BUT, you still need to adjust the circle's frame, when you modify the image view's frame. (and the cornerRadius accordingly). For example
DispatchQueue.main.async
{
ViewController.wrist_band_UIImageView.frame = CGRect( x: screen_position_x,
y: screen_position_y,
width: App_class.screen_width,
height: App_class.screen_height)
circleView.frame = CGRect( x: screen_position_x,
y: screen_position_y,
width: App_class.screen_width,
height: App_class.screen_height)
circleView.layer.cornerRadius = App_class.screen_width
}
And you need to add a variable to the viewController holding the reference to the circle view, like:
var circleView: UIView!
The following answer works.
The answers from d.mika above did not work. Plus, he was insulting. ;-)
// Show red circle
let circlePath = UIBezierPath(arcCenter: CGPoint(x: 200, y: 400), radius: CGFloat(40), startAngle: CGFloat(0), endAngle: CGFloat(Double.pi * 2), clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
// Change the fill color
shapeLayer.fillColor = UIColor.red.cgColor
// You can change the stroke color
shapeLayer.strokeColor = UIColor.red.cgColor
// You can change the line width
shapeLayer.lineWidth = 3.0
view.layer.addSublayer(shapeLayer)

How to add a value within Circle Swift

I have added a imageview which is displayed as a circle by using the code below.
ImageView.layer.cornerRadius = ImageView.frame.size.width/2
ImageView.clipsToBounds = true
how can I implement a label within this which displays a number. I am not sure how to go about doing this. I am quite new to swift therefore I am not if this can be done or if there are better ways about doing this. My goal is to create a custom circle to display within the imageview which displays a number.
Or is it better to embed a view and then use uibeziapath to construct a circle.
You can create a custom view using CAShapeLayer and UIBezierPath if you want a hollow circle and label inside it. This class does that.
class CircleView: UIView {
let shapeLayer = CAShapeLayer()
var circularPath: UIBezierPath?
// This is your label, you can its property here like textColor and Font
let breathingStatusLabel : UILabel = {
let label = UILabel()
label.textColor = UIColor.white
label.text = “1”
label.textAlignment = .center
label.font = UIFont(name: "AvenirNext-UltraLight", size: 31)
label.alpha = 1.0
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.frame = frame
self.setupPaths()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setupPaths() {
// Add label to your view and set its frame
self.addSubview(breathingStatusLabel)
breathingStatusLabel.frame = CGRect.init(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height)
breathingStatusLabel.center = CGPoint.init(x: self.bounds.width/2, y: self.bounds.height/2)
circularPath = UIBezierPath(arcCenter: .zero, radius: self.bounds.height / 2, startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
shapeLayer.path = circularPath?.cgPath
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 6.0
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.position = CGPoint(x: self.center.x, y: self.bounds.height / 2)
shapeLayer.lineCap = kCALineCapRound
shapeLayer.strokeEnd = 1
self.layer.addSublayer(shapeLayer)
self.shapeLayer.transform = CATransform3DMakeRotation(CGFloat.pi / 2, 0, 0, 1)
}
}
To use it :
let circle = CircleView(frame: someView.frame) // Some frame is where you want to show this.
view.addSubview(circle)
My goal is to create a custom circle to display within the imageview which displays a number.
If you are not using any image in imageView but you just make a circle then its better if you use
mylabel.layer.cornerRadius = mylabel.frame.size.width/2
and set data as mylabel.text = "2" and make sure it is center aligned.

Curv view at the bottom of the uiimageview

I need to draw a view with UIBezierPath at the bottom of the view just like this image.
I try with this code but oval draw at the half of the image not at bottom of the view like in picture
Here is my output and code
override func draw(_ rect: CGRect) {
super.draw(rect)
let shapeLayer = CAShapeLayer(layer: imgCamera.layer)
shapeLayer.path = self.pathCurvedForView(givenView: imgCamera).cgPath
shapeLayer.frame = imgCamera.bounds
shapeLayer.masksToBounds = true
imgCamera.layer.mask = shapeLayer
}
private func pathCurvedForView(givenView: UIView) ->UIBezierPath
{
let ovalRect = givenView.frame
let ovalPath = UIBezierPath()
ovalPath.addArc(withCenter: CGPoint(x: ovalRect.midX, y: ovalRect.midY), radius: ovalRect.width / 2, startAngle: 0 * CGFloat.pi/180, endAngle: -180 * CGFloat.pi/180, clockwise: true)
ovalPath.addLine(to: CGPoint(x: ovalRect.midX, y: ovalRect.midY))
ovalPath.close()
UIColor.gray.setFill()
ovalPath.fill()
return ovalPath
}
Code Work:
imageView.layer.cornerRadius = imageView.frame.width/2
imageView.layer.borderWidth = 5
imageView.layer.borderColor = UIColor.white.cgColor
let gradientLayer = CAGradientLayer()
gradientLayer.frame = imageView.bounds
gradientLayer.colors = [UIColor.clear.cgColor, UIColor.clear.cgColor, UIColor.clear.cgColor, lightBlack.cgColor , lightBlack.cgColor]
gradientLayer.locations = [NSNumber(value: 0.0), NSNumber(value: 0.5), NSNumber(value: 0.75),NSNumber(value: 0.75), NSNumber(value: 1.0)]
imageView.layer.addSublayer(gradientLayer)
Output :
You can add the cam UIButton directly as subView instead and using the UIImageView maskToBounds to achieve that effect, adding border width and border color to the UIImageView and making the UIImageView touchable is enough
Example Code
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
imageView.isUserInteractionEnabled = true
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.imageView.layer.cornerRadius = imageView.frame.size.height/2
self.imageView.layer.borderColor = UIColor.white.cgColor
self.imageView.layer.borderWidth = 2
self.imageView.layer.masksToBounds = true
self.view.backgroundColor = UIColor.gray
let button = UIButton(type: .custom)
button.backgroundColor = UIColor.black.withAlphaComponent(0.3)
button.frame = CGRect(x: 0, y: self.imageView.frame.size.height - self.imageView.frame.size.height * 0.2, width: self.imageView.frame.size.width, height: self.imageView.frame.size.height * 0.2)
button.setImage(UIImage(named: "chatBubbleChat2Filled"), for: .normal)
self.imageView.addSubview(button)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
startAngle and endAngle are supposed to be values in radians. The angle calculation looks really weird in your case, what were you trying to achieve? To me, the desired output look like it needs an arc from 5/8 PI to 7/8 PI, counter-clockwise. In your case, it goes from 0 to ~-PI, clockwise.
Try
ovalPath.addArc(withCenter: CGPoint(x: ovalRect.midX, y: ovalRect.midY), radius: ovalRect.width / 2, startAngle: CGFloat.pi * 5.0/8.0, endAngle: CGFloat.pi * 7.0/8.0, clockwise: false)
ovalPath.close()
You don't need to add addLine by the way, close() will do that fot you

Change UIButton rect without drawRect

I have a UIButton on xib which is a rectangle, I want to change it's frame to have an arrow like corner, but keep it on the xib (from interface builder), how can I do that?
I know that I can do that with UIBezierPath in drawRect, but that is only to create a custom shape and add it from code.
Is there another way?
This is my code with custom button class:
class ButtonContinue: UIButton {
var path: UIBezierPath!
override func awakeFromNib() {
backgroundColor = UIColor.green
addTarget(self, action: #selector(touchDown), for: .touchDown)
}
override func draw(_ rect: CGRect) {
path = UIBezierPath()
path.move(to: CGPoint(x: 32, y: 28 + (rect.size.height / 2)))
path.addLine(to: CGPoint(x: 70, y: 28))
path.addLine(to: CGPoint(x: rect.size.width, y: 28))
path.addLine(to: CGPoint(x: rect.size.width, y: rect.size.height + 28))
path.addLine(to: CGPoint(x: 70, y: rect.size.height + 28))
path.close()
let shapeLayer = CAShapeLayer()
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.fillColor = UIColor.blue.cgColor
shapeLayer.path = path.cgPath
layer.addSublayer(shapeLayer)
}
func touchDown(button: ButtonContinue, event: UIEvent) {
if let touch = event.touches(for: button)?.first {
let location = touch.location(in: button)
if path.contains(location) == false {
button.cancelTracking(with: nil)
}
}
}
}
Just put the image you provided in assets of the project. And set buttons image to the image in assets.
self.imageView.image = UIImage(named: "imageName")
And if you would like to change color of the lines of the image you can set the image rendering to alwaysTemplate
self.imageView?.image = UIImage(named: "imageName")?.withRenderingMode(UIImageRenderingMode.alwaysTemplate)
self.imageView?.tintColor = .red

Resources