Increasing the corner radius of UITextField removes its shadow - ios

I have a UITextField with custom code to control the corner radius and the placeholder color and another different properties. Also, I have a Protocol with Extension to drop shadow from any UI element.
The problem is: whenever I increase the corner radius of the text field, I lose the shadow. As long as the corner radius is 0, I still have a shadow.
And this shows at the debugger when I increase the cornerRadius and lose the shadow:
setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key height
Here is my Protocol I implemented to drop shadow:
import UIKit
protocol DropShadow {}
extension DropShadow where Self: UIView {
func addDropShadow() {
layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 0.7
layer.shadowOffset = CGSize(width: 0, height: 4)
layer.shadowRadius = 3
}
}
And here is my custom class for the UITextField:
import UIKit
#IBDesignable
class FancyTextField: UITextField, DropShadow {
#IBInspectable var cornerRadius: CGFloat = 0 {
didSet {
layer.cornerRadius = cornerRadius
layer.masksToBounds = cornerRadius > 0
}
}
#IBInspectable var borderWidth: CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
#IBInspectable var borderColor: UIColor? {
didSet {
layer.borderColor = borderColor?.cgColor
}
}
#IBInspectable var bgColor: UIColor? {
didSet {
backgroundColor = bgColor
}
}
#IBInspectable var placeHolderColor: UIColor? {
didSet {
let rawString = attributedPlaceholder?.string != nil ? attributedPlaceholder!.string : ""
let str = NSAttributedString(string: rawString, attributes: [NSForegroundColorAttributeName: placeHolderColor!])
attributedPlaceholder = str
}
}
}

When you add a corner radius to a UIView you have to set the clipsToBounds or masksToBounds to be true. That doesn't allow the shadow to be created as the shadow is created outside the bounds.
As a solution for this problem, you will have to create a superView to the UIView that has the clipped corners, and add a shadow to this superView (Make sure to set the superview to clear color)

Related

How to animate the shadow border of an UIView at the same time its content grows

I am working on a project which has all views embedded in a view container like this:
Today, I have to make one of these containers grow vertically in a animatable way when a stack view is showing its arranged subviews. So, the first problem was that the shadow border of this container is not being animated and I realized that the shadow path should be animated in another way and not just in a UIView.animate block.
I have isolated the problem in a small project:
As you can see, I am trying to deploy/undeploy the container view depending on the inner stackview arranged subviews. The problem is the shadow path, which I am already trying to understand how it works.
Can you help me to understand how to manage a UIView shadow layer when you need to update the height of an UIView in an animation block?
Container Shadow view code:
import UIKit
#IBDesignable final class ShadowView: UIView {
#IBInspectable var cornerRadius: CGFloat = 0.0 {
didSet {
setNeedsLayout()
}
}
#IBInspectable var shadowColor: UIColor = UIColor.darkGray {
didSet {
setNeedsLayout()
}
}
#IBInspectable var shadowOffsetWidth: CGFloat = 0.0 {
didSet {
setNeedsLayout()
}
}
#IBInspectable var shadowOffsetHeight: CGFloat = 1.8 {
didSet {
setNeedsLayout()
}
}
#IBInspectable var shadowOpacity: Float = 0.30 {
didSet {
setNeedsLayout()
}
}
#IBInspectable var shadowRadius: CGFloat = 3.0 {
didSet {
setNeedsLayout()
}
}
#IBInspectable var isAnimatable: Bool = false
private var shadowLayer: CAShapeLayer = CAShapeLayer() {
didSet {
setNeedsLayout()
}
}
private var previousPat: CGPath = CGPath(rect: CGRect(x: 0, y: 0, width: 0, height: 0), transform: .none)
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = cornerRadius
shadowLayer.path = UIBezierPath(roundedRect: bounds,
cornerRadius: cornerRadius).cgPath
shadowLayer.fillColor = backgroundColor?.cgColor
shadowLayer.shadowColor = shadowColor.cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowOffset = CGSize(width: shadowOffsetWidth,
height: shadowOffsetHeight)
shadowLayer.shadowOpacity = shadowOpacity
shadowLayer.shadowRadius = shadowRadius
if isAnimatable {
let animation = CABasicAnimation(keyPath: "shadowPath")
animation.fromValue = previousPat
animation.toValue = shadowLayer.path
animation.autoreverses = false
animation.duration = 0.8
shadowLayer.add(animation, forKey: "pathAnimation")
}
layer.insertSublayer(shadowLayer, at: 0)
previousPat = shadowLayer.path!
}
}
Main ViewController code:
final class ViewController: UIViewController {
#IBOutlet weak var stack: UIStackView!
#IBOutlet weak var redContainerLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func animateAction(_ sender: Any) {
UIView.animate(withDuration: 0.8) {
self.redContainerLabel.alpha = self.redContainerLabel.alpha == 0 ? 1 : 0
self.stack.arrangedSubviews.forEach {
$0.isHidden.toggle()
}
}
}
}
View structure:
I was looking for an answer to exactly this problem and encountered this post here, which I believe is what you're looking for:
Animating CALayer shadow simultaneously as UITableviewCell height animates
The solution is a bit verbose (I'm surprised the use-case is so rare as to not prompt apple to fix(?) it) and a few years old at this point, but the most recent I could find. Ultimately I decided to work around it by fading out my shadows before resizing, but you might find it useful!
A couple of other, older posts I've found appear to corroborated the level of effort required for this effect (again, Apple's got a weird list of things it decides are your problem), so that first post is probably your best bet.
https://petosoft.wordpress.com/2012/10/24/shadowpath-not-animating-with-standard-uiview-animations-on-ipad/
Smoothly rotate and change size of UIView with shadow
UIView: How to animate CALayer frame with shadow?
If you've managed to find a more straightforward solution, by all means let me know; in the mean-time I hope this helps.

UIView shadow issue on real device - iOS

I have a tableview cell with view inside and I want to add shadow to it.
For this reason I add this code:
self.PrimaView.layer.shadowPath = UIBezierPath(rect: self.PrimaView.bounds).cgPath
self.PrimaView.layer.shadowRadius = 3
self.PrimaView.layer.shadowOffset = .zero
self.PrimaView.layer.shadowOpacity = 3
This is tableview cell structure:
My problem is this:
when I execute code on simulator I get this (this is what I want to get):
but when I execute the same code on the real device I get this:
Why is it different than the previous image?
#Sulthan is right or may be you have safe area issue as well if you have same device if not.
Then you can use this code for shadow
import UIKit
#IBDesignable
class CardViewMaterial: UIView {
// #IBInspectable var cornerRadius: CGFloat = 2
#IBInspectable var shadowOffsetWidth: Int = 0
#IBInspectable var shadowOffsetHeight: Int = 3
#IBInspectable var shadowColor: UIColor? = UIColor.black
#IBInspectable var shadowOpacity: Float = 0.5
override func layoutSubviews() {
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
}
}

Add border to all labels in a view

In my app I have a custom view containerView in this view there are more than 20 labels and I want to apply a border style to all of them.
Is there a way to avoid to add the border to each of them avoiding to have a long list?
Something similar to:
for each label in containerView {
labels.layer.borderColor = UIColor.greenColor.CGColor
}
create a subclass like this:
#IBDesignable
class BorderedLabel: UILabel {
#IBInspectable var borderColor: UIColor? {
didSet {
layer.borderColor = borderColor?.CGColor
}
}
#IBInspectable var borderWidth: CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
}
then change your label's custom class in interface builder, set the borderColor / borderWidth properties you like and see the results live in interface builder!
You need to set borderWidth.
for subview in self.view.containerView.subviews as! [UIView] {
if let label = subview as? UILabel {
label.layer.borderColor = UIColor.greenColor().CGColor
label.layer.borderWidth = 1
}
}
Swift 4
label.layer.borderColor = UIColor.green.cgColor
You can use this:
for view in self.view.containerView.subviews as! [UIView] {
if let label = view as? UITextField {
label.layer.borderColor = UIColor.blueColor().CGColor;
label.layer.borderWidth = 1;
}
}
}
My solution is:
for imageViews in self.containerView.subviews as! [UIImageView] {
imageViews.layer.borderColor = UIColor.greenColor().CGColor
imageViews.layer.borderWidth = 1
}

Set a border for UIButton in Storyboard

I can't get a border onto my buttons in Xcode 5 without setting them directly in the code. Is it possible that there's no way to do this on the storyboard without making a custom background image?
You can use key path.
For example the corner radius (layer.cornerRadius) as describe on the image.
You will not be able to see the effects on storyboard, cause this parameters are evaluated at runtime. Now you can use a swift category in UIView (code bellow the picture) in with #IBInspectable to show the result at the storyboard (If you are using the category, use only cornerRadius and not layer.cornerRadius as a key path.
extension UIView {
#IBInspectable var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
}
Here is category from Peter DeWeese answer that allow use keypath layer.borderUIColor to set the border color.
CALayer+XibConfiguration.h:
#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>
#interface CALayer(XibConfiguration)
// This assigns a CGColor to borderColor.
#property(nonatomic, assign) UIColor* borderUIColor;
#end
CALayer+XibConfiguration.m:
#import "CALayer+XibConfiguration.h"
#implementation CALayer(XibConfiguration)
-(void)setBorderUIColor:(UIColor*)color
{
self.borderColor = color.CGColor;
}
-(UIColor*)borderUIColor
{
return [UIColor colorWithCGColor:self.borderColor];
}
#end
Swift 3
If you want to see the result in IB when you use IBInspectable, you have to extend UIView and add the properties to that class, i.e.
#IBDesignable class MyView: UIView {}
extension MyView {
#IBInspectable var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
#IBInspectable var borderWidth: CGFloat {
get {
return layer.borderWidth
}
set {
layer.borderWidth = newValue
layer.masksToBounds = newValue > 0
}
}
#IBInspectable var borderColor: UIColor {
get {
return UIColor.init(cgColor: layer.borderColor!)
}
set {
layer.borderColor = newValue.cgColor
}
}
}
reference: http://nshipster.com/ibinspectable-ibdesignable/
Short answer :
layer.cornerRadius = 10
layer.borderWidth = 1
layer.borderColor = UIColor.blue.cgColor
Long answer :
Rounded Corners UIButton
customUIView.layer.cornerRadius = 10
Border Thickness
pcustomUIView.layer.borderWidth = 1
Border Color
customUIView.layer.borderColor = UIColor.blue.cgColor
you can set your UIButton User Defined Run Time Attributes ie
borderWidth, cornerRadius, borderColor etc. As shown in the image.
Note:- don't use layer. before the attribute name, it will not work.
You can use a piece of code like:
self.addButton.layer.borderColor = [[UIColor greenColor] CGColor];
Please note: addButton is an IBOutlet.

Cocoa Touch: How To Change UIView's Border Color And Thickness?

I saw in the inspector that I can change the background color, but I'd like to also change the border color and thickness, is this possible?
You need to use view's layer to set border property. e.g:
#import <QuartzCore/QuartzCore.h>
...
view.layer.borderColor = [UIColor redColor].CGColor;
view.layer.borderWidth = 3.0f;
You also need to link with QuartzCore.framework to access this functionality.
Xcode 6 update
Since Xcode's newest version there is a better solution to this:
With #IBInspectable you can set Attributes directly from within the Attributes Inspector.
This sets the User Defined Runtime Attributes for you:
There are two approaches to set this up:
Option 1 (with live updating in Storyboard)
Create MyCustomView.
This inherits from UIView.
Set #IBDesignable (this makes the View update live).*
Set your Runtime Attributes (border, etc.) with #IBInspectable
Change your Views Class to MyCustomView
Edit in Attributes Panel and see changes in Storyboard :)
`
#IBDesignable
class MyCustomView: UIView {
#IBInspectable var cornerRadius: CGFloat = 0 {
didSet {
layer.cornerRadius = cornerRadius
layer.masksToBounds = cornerRadius > 0
}
}
#IBInspectable var borderWidth: CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
#IBInspectable var borderColor: UIColor? {
didSet {
layer.borderColor = borderColor?.CGColor
}
}
}
* #IBDesignable only works when set at the start of class MyCustomView
Option 2 (not working since Swift 1.2, see comments)
Extend your UIView Class:
extension UIView {
#IBInspectable var cornerRadius: CGFloat = 0 {
didSet {
layer.cornerRadius = cornerRadius
layer.masksToBounds = cornerRadius > 0
}
}
#IBInspectable var borderWidth: CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
#IBInspectable var borderColor: UIColor? {
didSet {
layer.borderColor = borderColor?.CGColor
}
}
}
This way, your default View always has those extra editable fields in Attributes Inspector. Another advantage is that you don't have to change the class to MycustomView every time.
However, one drawback to this is that you will only see your changes when you run your app.
You can also create border with the color of your wish..
view.layer.borderColor = [UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:1.0].CGColor;
*r,g,b are the values between 0 to 255.
Add following #IBInspectables in UIView extension
extension UIView {
#IBInspectable var borderWidth: CGFloat {
get {
return layer.borderWidth
}
set(newValue) {
layer.borderWidth = newValue
}
}
#IBInspectable var borderColor: UIColor? {
get {
if let color = layer.borderColor {
return UIColor(CGColor: color)
}
return nil
}
set(newValue) {
layer.borderColor = newValue?.CGColor
}
}
}
And then you should be able to set borderColor and borderWidth attributes directly from Attribute inspector. See attached image
Attributes Inspector
view.layer.borderWidth = 1.0
view.layer.borderColor = UIColor.lightGray.cgColor
When I use Vladimir's CALayer solution, and on top of the view I have an animation, like a modal UINavigationController dismissing, I see a lot of glitches happening and having drawing performance issues.
So, another way to achieve this, but without the glitches and performance loss, is to make a custom UIView and implement the drawRect message like so:
- (void)drawRect:(CGRect)rect
{
CGContextRef contextRef = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(contextRef, 1);
CGContextSetRGBStrokeColor(contextRef, 255.0, 255.0, 255.0, 1.0);
CGContextStrokeRect(contextRef, rect);
}
Try this code:
view.layer.borderColor = [UIColor redColor].CGColor;
view.layer.borderWidth= 2.0;
[view setClipsToBounds:YES];
I wouldn't suggest overriding the drawRect due to causing a performance hit.
Instead, I would modify the properties of the class like below (in your custom uiview):
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.layer.borderWidth = 2.f;
self.layer.borderColor = [UIColor redColor].CGColor;
}
return self;
I didn't see any glitches when taking above approach - not sure why putting in the initWithFrame stops these ;-)
I wanted to add this to #marczking's answer (Option 1) as a comment, but my lowly status on StackOverflow is preventing that.
I did a port of #marczking's answer to Objective C. Works like charm, thanks #marczking!
UIView+Border.h:
#import <UIKit/UIKit.h>
IB_DESIGNABLE
#interface UIView (Border)
-(void)setBorderColor:(UIColor *)color;
-(void)setBorderWidth:(CGFloat)width;
-(void)setCornerRadius:(CGFloat)radius;
#end
UIView+Border.m:
#import "UIView+Border.h"
#implementation UIView (Border)
// Note: cannot use synthesize in a Category
-(void)setBorderColor:(UIColor *)color
{
self.layer.borderColor = color.CGColor;
}
-(void)setBorderWidth:(CGFloat)width
{
self.layer.borderWidth = width;
}
-(void)setCornerRadius:(CGFloat)radius
{
self.layer.cornerRadius = radius;
self.layer.masksToBounds = radius > 0;
}
#end
#IBInspectable is working for me on iOS 9 , Swift 2.0
extension UIView {
#IBInspectable var borderWidth: CGFloat {
get {
return layer.borderWidth
}
set(newValue) {
layer.borderWidth = newValue
}
}
#IBInspectable var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set(newValue) {
layer.cornerRadius = newValue
}
}
#IBInspectable var borderColor: UIColor? {
get {
if let color = layer.borderColor {
return UIColor(CGColor: color)
}
return nil
}
set(newValue) {
layer.borderColor = newValue?.CGColor
}
}
If you didn't want to edit the layer of a UIView, you could always embed the view within another view. The parent view would have its background color set to the border color. It would also be slightly larger, depending upon how wide you want the border to be.
Of course, this only works if your view isn't transparent and you only want a single border color. The OP wanted the border in the view itself, but this may be a viable alternative.
item's border color in swift 4.2:
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell_lastOrderId") as! Cell_lastOrder
cell.layer.borderWidth = 1
cell.layer.borderColor = UIColor.white.cgColor
cell.layer.cornerRadius = 10
If you want to add different border on different sides, may be add a subview with the specific style is a way easy to come up with.
[self.view.layer setBorderColor: [UIColor colorWithRed:0.265 green:0.447 blue:0.767 alpha:1.0f].CGColor];

Resources