Add border to all labels in a view - ios

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
}

Related

TextView placeholder being cut off when hidden inside StackView

I have a TextView that I have placed inside a UIView, which is then placed inside a StackView, like so:
- UIStackView
- UIView
- UITextView
I also looked up a custom extension for UITextView which allows me to add a placeholder in the text view:
import UIKit
extension UITextView {
private class PlaceholderLabel: UILabel { }
private var placeholderLabel: PlaceholderLabel {
if let label = subviews.compactMap( { $0 as? PlaceholderLabel }).first {
return label
} else {
let label = PlaceholderLabel(frame: .zero)
label.font = font
addSubview(label)
return label
}
}
#IBInspectable
var placeholder: String {
get {
return subviews.compactMap( { $0 as? PlaceholderLabel }).first?.text ?? ""
}
set {
let placeholderLabel = self.placeholderLabel
placeholderLabel.text = newValue
placeholderLabel.numberOfLines = 0
let width = frame.width - textContainer.lineFragmentPadding * 2
let size = placeholderLabel.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude))
placeholderLabel.frame.size.height = size.height
placeholderLabel.frame.size.width = width
placeholderLabel.frame.origin = CGPoint(x: textContainer.lineFragmentPadding, y: textContainerInset.top)
textStorage.delegate = self
}
}
#IBInspectable
var placeholderColor: UIColor? {
get {
self.placeholderColor
}
set {
placeholderLabel.textColor = newValue
}
}
}
extension UITextView: NSTextStorageDelegate {
public func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorage.EditActions, range editedRange: NSRange, changeInLength delta: Int) {
if editedMask.contains(.editedCharacters) {
placeholderLabel.isHidden = !text.isEmpty
}
}
}
However, when I set the UIView to be hidden inside the stack view, the placeholder is cut off after the UIView is made visible:
If I don't set the UIView to be hidden, it shows fine:
I found out that the width of the UIView inside the stack view changes when I set it to hidden:
As you can see, it's no longer full-width when hidden. Same with the UITextView:
My suspicion is that the placeholder text's constraints aren't being reset properly when the UIView is shown.
What should I do to fix this?
It seems like -
even though the UIStackView is doing the correct thing by making the UIView.width/height == 0 when you mark it .isHidden = true
contents inside this UIView are still visible outside it's bounds.
You could try setting clipsToBounds = true for your UIView that holds the UITextView.
- UIStackView
- UIView ///// Try setting `clipsToBounds = true` for this one
- UITextView

Efficient off-screen UIView rendering and mirroring

I have a "off-screen" UIView hierarchy which I want render in different locations of my screen. In addition it should be possible to show only parts of this view hierarchy and should reflect all changes made to this hierarchy.
The difficulties:
The UIView method drawHierarchy(in:afterScreenUpdates:) always calls draw(_ rect:) and is therefore very inefficient for large hierarchies if you want to incorporate all changes to the view hierarchy. You would have to redraw it every screen update or observe all changing properties of all views. Draw view hierarchy documentation
The UIView method snapshotView(afterScreenUpdates:) also does not help much since I have not found a way to get a correct view hierarchy drawing if this hierarchy is "off-screen". Snapshot view documentation
"Off-Screen": The root view of this view hierarchy is not part of the UI of the app. It has no superview.
Below you can see a visual representation of my idea:
Here's how I would go about doing it. First, I would duplicate the view you are trying to duplicate. I wrote a little extension for this:
extension UIView {
func duplicate<T: UIView>() -> T {
return NSKeyedUnarchiver.unarchiveObject(with: NSKeyedArchiver.archivedData(withRootObject: self)) as! T
}
func copyProperties(fromView: UIView, recursive: Bool = true) {
contentMode = fromView.contentMode
tag = fromView.tag
backgroundColor = fromView.backgroundColor
tintColor = fromView.tintColor
layer.cornerRadius = fromView.layer.cornerRadius
layer.maskedCorners = fromView.layer.maskedCorners
layer.borderColor = fromView.layer.borderColor
layer.borderWidth = fromView.layer.borderWidth
layer.shadowOpacity = fromView.layer.shadowOpacity
layer.shadowRadius = fromView.layer.shadowRadius
layer.shadowPath = fromView.layer.shadowPath
layer.shadowColor = fromView.layer.shadowColor
layer.shadowOffset = fromView.layer.shadowOffset
clipsToBounds = fromView.clipsToBounds
layer.masksToBounds = fromView.layer.masksToBounds
mask = fromView.mask
layer.mask = fromView.layer.mask
alpha = fromView.alpha
isHidden = fromView.isHidden
if let gradientLayer = layer as? CAGradientLayer, let fromGradientLayer = fromView.layer as? CAGradientLayer {
gradientLayer.colors = fromGradientLayer.colors
gradientLayer.startPoint = fromGradientLayer.startPoint
gradientLayer.endPoint = fromGradientLayer.endPoint
gradientLayer.locations = fromGradientLayer.locations
gradientLayer.type = fromGradientLayer.type
}
if let imgView = self as? UIImageView, let fromImgView = fromView as? UIImageView {
imgView.tintColor = .clear
imgView.image = fromImgView.image?.withRenderingMode(fromImgView.image?.renderingMode ?? .automatic)
imgView.tintColor = fromImgView.tintColor
}
if let btn = self as? UIButton, let fromBtn = fromView as? UIButton {
btn.setImage(fromBtn.image(for: fromBtn.state), for: fromBtn.state)
}
if let textField = self as? UITextField, let fromTextField = fromView as? UITextField {
if let leftView = fromTextField.leftView {
textField.leftView = leftView.duplicate()
textField.leftView?.copyProperties(fromView: leftView)
}
if let rightView = fromTextField.rightView {
textField.rightView = rightView.duplicate()
textField.rightView?.copyProperties(fromView: rightView)
}
textField.attributedText = fromTextField.attributedText
textField.attributedPlaceholder = fromTextField.attributedPlaceholder
}
if let lbl = self as? UILabel, let fromLbl = fromView as? UILabel {
lbl.attributedText = fromLbl.attributedText
lbl.textAlignment = fromLbl.textAlignment
lbl.font = fromLbl.font
lbl.bounds = fromLbl.bounds
}
if recursive {
for (i, view) in subviews.enumerated() {
if i >= fromView.subviews.count {
break
}
view.copyProperties(fromView: fromView.subviews[i])
}
}
}
}
to use this extension, simply do
let duplicateView = originalView.duplicate()
duplicateView.copyProperties(fromView: originalView)
parentView.addSubview(duplicateView)
Then I would mask the duplicate view to only get the particular section that you want
let mask = UIView(frame: CGRect(x: 0, y: 0, width: yourNewWidth, height: yourNewHeight))
mask.backgroundColor = .black
duplicateView.mask = mask
finally, I would scale it to whatever size you want using CGAffineTransform
duplicateView.transform = CGAffineTransform(scaleX: xScale, y: yScale)
the copyProperties function should work well but you can change it if necessary to copy even more things from one view to another.
Good luck, let me know how it goes :)
I'd duplicate the content I wish to display and crop it as I want.
Let's say I have a ContentViewController which carries the view hierarchy I wish to replicate. I would encapsule all the changes that can be made to the hierarchy inside a ContentViewModel. Something like:
struct ContentViewModel {
let actionTitle: String?
let contentMessage: String?
// ...
}
class ContentViewController: UIViewController {
func display(_ viewModel: ContentViewModel) { /* ... */ }
}
With a ClippingView (or a simple UIScrollView) :
class ClippingView: UIView {
var contentOffset: CGPoint = .zero // a way to specify the part of the view you wish to display
var contentFrame: CGRect = .zero // the actual size of the clipped view
var clippedView: UIView?
override init(frame: CGRect) {
super.init(frame: frame)
clipsToBounds = true
}
override func layoutSubviews() {
super.layoutSubviews()
clippedView?.frame = contentFrame
clippedView?.frame.origin = contentOffset
}
}
And a view controller container, I would crop each instance of my content and update all of them each time something happens :
class ContainerViewController: UIViewController {
let contentViewControllers: [ContentViewController] = // 3 in your case
override func viewDidLoad() {
super.viewDidLoad()
contentViewControllers.forEach { viewController in
addChil(viewController)
let clippingView = ClippingView()
clippingView.clippedView = viewController.view
clippingView.contentOffset = // ...
viewController.didMove(to: self)
}
}
func somethingChange() {
let newViewModel = ContentViewModel(...)
contentViewControllers.forEach { $0.display(newViewModel) }
}
}
Could this scenario work in your case ?

UIView not shows round

For making a circular UIView I am using the cornerRadius property.
I have a UIView with dimension 79*158.
redView.layer.cornerRadius = redView.frame.size.height/2
redView.layer.masksToBounds = true
It shows elipse instead of circle:
Any workaround or does it only work with square type (eg. UIView(100*100))?
I am ok if it resizes dynamically.
use this...
func makeCircle (view: UIView) {
view.clipsToBounds = true
let height = view.frame.size.height
let width = view.frame.size.width
let newHeight = min(height, width) // use "max" if you want big circle
var rectFrame = view.frame
rectFrame.size.height = newHeight
rectFrame.size.width = newHeight
view.frame = rectFrame
view.layer.cornerRadius = newHeight/2
}
use like this:
#IBOutlet var rectView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
makeCircle(view: rectView)
}
You have a UIView with dimension 79*158.So that is wrong. You should have exactly same height and width for rounding exact a view to circle shape.
E.g.
redView.frame.size.height = 79.0
redView.frame.size.width = 79.0
or
redView.frame.size.height = 158.0
redView.frame.size.width = 158.0
And apply corner radius like:
redView.clipsToBounds = true
redView.layer.cornerRadius = redView.frame.size.height / 2.0
Result:
Note: Check your constrains also If you are using Auto Layout. Be sure view frame doesn't change.
If you are using constraints then changing the frame/bounds of the view is not a good idea. Instead you should do the following.
If the view is contained in a UIViewController then set the cornerRadius in viewDidLayoutSubviews method
And if the view is itself a subclass of UIView the set the cornerRadius in layoutSubviews method
Only Squire view make a perfect circle. For example, if your view size is (10*10),(50*50),(100*100), etc. then your view becomes perfect squire else not.
Using IBDesignable, you can display without project run in storyboard ox .XIB #simple way
Step 1. Subclass UIView:
#IBDesignable class RoundedCornerView: UIView {
#IBInspectable var borderWidth:CGFloat = 2 {
didSet {
layer.borderWidth = borderWidth
}
}
#IBInspectable var borderColor:UIColor = UIColor.orangeGradientLight {
didSet {
layer.borderColor = borderColor.cgColor
}
}
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = frame.height/2
layer.masksToBounds = true
layer.borderColor = borderColor.cgColor
layer.borderWidth = borderWidth
}
}
Step 2. Set custom class in identity inspector:
can't.
Try resize UIView to square: 79*79 OR 158*158
And set:
redView.layer.cornerRadius = redView.frame.size.height/2

Increasing the corner radius of UITextField removes its shadow

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)

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