class RoundImage: UIImageView {
override func awakeFromNib() {
super.awakeFromNib()
setupView()
}
...
func setupView() {
self.layer.borderWidth = borderWidth
self.layer.borderColor = borderColor
self.clipsToBounds = clip
self.layer.cornerRadius = self.frame.size.width / 2
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setupView()
}
}
I am using the following class to style my UIImageViews. I want to make them circular. When I add the following line of code self.layer.cornerRadius = self.frame.size.width / 2 to make this possible, my entire image disappears.
When I use a hardcoded value like 50 (since the width of my image is 100) it works. But when I want to make it dynamic this way, it seems to fail.
What am I missing here?
I faced with this problem a few days ago.
You should add layoutIfNeeded() before layer modifications.
In your case:
func setupView() {
self.layoutIfNeeded()
self.layer.borderWidth = borderWidth
self.layer.borderColor = borderColor
self.clipsToBounds = clip
self.layer.cornerRadius = self.frame.size.width / 2
}
When this code gets run the view hasn't been sized yet, so the width is incorrect. Try putting a listener on frame:
override var frame : CGRect {
didSet {
layer.cornerRadius = frame.size.width / 2
}
}
Related
I am trying to create an overlay view, that has a "cutout" part that comes from a frame that is passed to the view, that size and position of that passed frame will change upon creation of the view. And in that "cutout" part I am expecting to see the content that is under that overlay view. Tried to set border to a rounded rectangle that is added to a CGMutablePath, but no luck.
The expected result is something like this:
The code I currently have in my UIView class, without tried solutions as I can't seem to get them to work properly. This current code displays the expected result, but without the red border:
override func draw(_ rect: CGRect) {
super.draw(rect)
UIColor.blue.setFill()
UIRectFill(rect)
let shapeLayer = CAShapeLayer()
let path = CGMutablePath()
// frame that will change position on the screen
if let frame = changingFrame {
path.addRoundedRect(in: frame, cornerWidth: 16, cornerHeight: 16)
}
path.addRect(bounds)
shapeLayer.path = path
shapeLayer.fillRule = CAShapeLayerFillRule.evenOdd
self.layer.mask = shapeLayer
}
I have tried solutions from here, here, but no luck as CAShapeLayer for border just overlays existing one.
What can I do differently to achieve the expected result? Thanks!
try this ⭐️
If all you want to do is create a rounded rectangle, then you can simply use.
let rectangle = CGRect(x: 0, y: 0, width: 100, height: 100)
let path = UIBezierPath(roundedRect: rectangle, cornerRadius: 20)
view.clipsToBounds = true
view.layer.cornerRadius = 10.0
let border = CAShapeLayer()
border.path = UIBezierPath(roundedRect:view.bounds, cornerRadius:10.0).cgPath
border.frame = view.bounds
border.fillColor = nil
border.strokeColor = UIColor.purple.cgColor
border.lineWidth = borderWidth * 2.0 // doubled since half will be clipped
border.lineDashPattern = [1.0]
view.layer.addSublayer(border)
One approach is to use two sublayers... a "cutout" layer and a "border" layer.
Use the same path for the cutout and the border shape, setting the line width and stroke color for the "outline".
Here's an example -- including making it #IBDesignable with a few #IBInspectable properties:
#IBDesignable
class BorderedCutoutView: UIView {
#IBInspectable
var bkgColor: UIColor = .systemBlue {
didSet {
setNeedsLayout()
}
}
#IBInspectable
var brdColor: UIColor = .white {
didSet {
setNeedsLayout()
}
}
#IBInspectable
var brdWidth: CGFloat = 1 {
didSet {
setNeedsLayout()
}
}
#IBInspectable
var radius: CGFloat = 20 {
didSet {
setNeedsLayout()
}
}
#IBInspectable
var horizInset: CGFloat = 40.0 {
didSet {
setNeedsLayout()
}
}
#IBInspectable
var vertInset: CGFloat = 60.0 {
didSet {
setNeedsLayout()
}
}
private let cutoutLayer = CAShapeLayer()
private let borderLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
backgroundColor = .clear
}
private func commonInit() -> Void {
backgroundColor = .clear
layer.addSublayer(cutoutLayer)
layer.addSublayer(borderLayer)
}
override func layoutSubviews() {
super.layoutSubviews()
let path = UIBezierPath(rect: bounds)
let cp = UIBezierPath(roundedRect: bounds.insetBy(dx: horizInset, dy: vertInset), cornerRadius: radius)
path.append(cp)
path.usesEvenOddFillRule = true
cutoutLayer.path = path.cgPath
cutoutLayer.fillRule = .evenOdd
cutoutLayer.fillColor = bkgColor.cgColor
borderLayer.path = cp.cgPath
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.lineWidth = brdWidth
borderLayer.strokeColor = brdColor.cgColor
}
}
This example uses horizontal and vertical "inset" values to center the cutout in the view.
Result:
I am following the following tutorial on how to round the sides of buttons:
https://blog.supereasyapps.com/how-to-create-round-buttons-using-ibdesignable-on-ios-11/
I have created a new Swift code file as it has suggested and entered the following code:
import Foundation
import UIKit
#IBDesignable class RoundButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
sharedInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
sharedInit()
}
override func prepareForInterfaceBuilder() {
sharedInit()
}
func sharedInit() {
refreshCorners(value: cornerRadius)
}
func refreshCorners(value: CGFloat) {
layer.cornerRadius = value
}
var cornerRadius: CGFloat = 15 {
didSet {
refreshCorners(value: cornerRadius)
}
}
}
I cannot see find the Corner Radius option in the "Identity Inspector" as shown in the animation on the website to create rounder buttons. Can someone please tell me what I did wrong? Greatly appreciate the help
You need to change the cornerRadius to the following, to include #IBInspectable:
#IBInspectable
var cornerRadius: CGFloat = 15 {
didSet {
refreshCorners(value: cornerRadius)
layer.masksToBounds = true // Could include this
}
}
#IBInspectable allows you to access the property within storyboards, the interface builder.
You could also include layer.masksToBounds = true, so the corners are sure to get rounded even if masksToBounds in the storyboard is unchecked. However, you cannot apply both rounded corners and a shadow at the same time, but there are many workarounds available.
You missed out adding the cornerRadius var:
#IBDesignable class RoundButton: UIButton {
#IBInspectable var cornerRadius: CGFloat = 15 { didSet { refreshCorners(value: cornerRadius) } }
override func awakeFromNib() {
super.awakeFromNib()
setupView()
}
.
.
.
.
}
It is my universal extension for UIView. You can move it to only for UIButton. Thus you can use 2 methods from interface to make rounded corners.
#IBDesignable extension UIView {
#IBInspectable var roundRadius : CGFloat {
set {
self.layer.cornerRadius = newValue
}
get {
return self.roundRadius
}
}
/// automatically set cornerRadius as half of height
#IBInspectable var isRounded : Bool {
set {
let radius = newValue ? self.frame.height/2 : 0
self.layer.cornerRadius = radius
}
get {
return self.isRounded
}
}
}
I tried to get a rounded corner with layer.cornerRadius for my segmented control but it does not work for me actually I want to get cornerRadius depending on the frame
here is my code
func setupView(){
layer.masksToBounds = true
// here my corner radius
layer.cornerRadius = frame.height / 2.0
layer.borderColor = UIColor.walkthroughOrangeAccent.cgColor
layer.borderWidth = 2
backgroundColor = UIColor.clear
setupImageView()
addIndividualItemConstraints(items: imageViewList, mainView: self, padding: 0)
insertSubview(thumbView, at: 0)
}
I suspect you're not seeing the corner radius because of the layout constraints. Try subclassing the segmented control and setting the corner radius in layoutSubviews:
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = frame.height / 2.0
}
Or, if you're in the view controller:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
segmentedControl.layer.cornerRadius = segmentedControl.frame.width / 2.0
}
Below is the code for a custom Card View. The problem is, when I add the subviews to this in Interface builder it doesn't apply the corner radius to the subview. For the most part, I can get away with this by making subviews have a clear background color but I'm struggling with UIImageView. When I add that to a card it ends up with pointy corners and I've not been able to fix it.
Various solutions on here have suggested adding a second layer to display the shadow. I've attempted this but it still doesn't work as intended. What I'm trying to achieve is a view with rounded corners, drop shadow and adding any subviews (such as UIImageView) should also maintain the corner radius and not pointing out.
I've tried various settings with layer.masksToBounds and self.clipsToBounds and I always seem to get subviews with a corner radius but no shadow or the shadow visible and views not clipping.
#IBDesignable class CardView: UIView {
#IBInspectable dynamic var cornerRadius: CGFloat = 6
#IBInspectable dynamic var shadowOffsetWidth: Int = 2
#IBInspectable dynamic var shadowOffsetHeight: Int = 2
#IBInspectable dynamic var shadowColor: UIColor? = UIColor(netHex: 0x333333)
#IBInspectable dynamic var shadowOpacity: Float = 0.5
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
commonInit()
}
override func prepareForInterfaceBuilder() {
commonInit()
}
func commonInit() {
layer.cornerRadius = cornerRadius
let shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
layer.masksToBounds = false
layer.shadowColor = shadowColor?.cgColor
layer.shadowOffset = CGSize(width: shadowOffsetWidth, height: shadowOffsetHeight)
layer.shadowOpacity = shadowOpacity
layer.shadowPath = shadowPath.cgPath
// This was how I tried to add a seperate shadow layer
// let shadowView = UIView(frame: self.frame)
// shadowView.layer.shadowColor = shadowColor?.cgColor
// shadowView.layer.shadowOffset = CGSize(width: shadowOffsetWidth, height: shadowOffsetHeight)
// shadowView.layer.shadowOpacity = shadowOpacity
// shadowView.layer.shadowPath = shadowPath.cgPath
// shadowView.layer.masksToBounds = false
//
// self.addSubview(shadowView)
}
}
The way you were trying to implement a second view to handle shadows is almost correct, you just didn't keep the right order.
Your CardView class already handles displaying a shadow. Leave that view as it is and instead add a UIView called "ContentView" as a subview. That content view has the same frame and corner radius as your CardView.
On the "ContentView", you don't need to do any work with shadows. Instead, set its layer's masksToBounds property to true. Now add all the content you want to display in your Card to the "ContentView" and it should clip correctly.
func commonInit() {
layer.cornerRadius = cornerRadius
let shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
layer.masksToBounds = false
layer.shadowColor = shadowColor?.cgColor
layer.shadowOffset = CGSize(width: shadowOffsetWidth, height: shadowOffsetHeight)
layer.shadowOpacity = shadowOpacity
layer.shadowPath = shadowPath.cgPath
let contentView = UIView()
contentView.frame = self.frame
contentView.layer.cornerRadius = cornerRadius
contentView.layer.masksToBounds = true
// any content you add should now be added to the contentView:
// contentView.addSubview(aView)
}
furthermore, you can specific corners.
layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMaxYCorner]
I want to set shadow outline (solid) around UIImageView text.
I tried below code: -
class BottomToolBarImageView: UIImageView {
override func layoutSubviews() {
super.layoutSubviews()
layer.shadowColor = UIColor.red.cgColor
layer.shadowOffset = CGSize(width: 5.0, height: 5.0)
layer.shadowOpacity = 1.0
layer.shadowRadius = 0.0
}
}
But this is not working as I required. Please suggest. Thanks in advance.
I required output like: -
To achieve this, you should add border to image view, instead of shadow.
class BottomToolBarImageView: UIImageView {
override func layoutSubviews() {
super.layoutSubviews()
layer.borderColor = UIColor.red.cgColor
layer.borderWidth = 4.0
layer.cornerRadius = self.frame.size.height/2
}
}
This should work.
P.S Kindly change cornerRadius according to required output.