Interface Builder Clipping Designable Views - ios

I really need a hand here. I have created an #IBDesignable subclass of UILabel which works fine in the XCode Interface Builder. However, even if I set 'clipsToBounds' to false, Interface Builder will still clip it whilst changing the #IBInspectable properties works.
If I'm running the app on simulator or device, the UILabel isn't clipped and gives me the desired results (whilst still applying the values that Interface Builder has).
BEFORE THE CHANGE (The subviews are visible)
AFTER THE CHANGE IN INTERFACE BUILDER (The subviews are out of view)
AFTER THE CHANGE IN SIMULATOR (The subviews are as expected)
Any help would be massively appreciated. The code for the Custom Class is below.
#IBDesignable class UIFeaturedLabel: UILabel {
#IBInspectable var borderWidth: Float = 4
#IBInspectable var borderOffsetX: Float = 15
#IBInspectable var borderOffsetY: Float = 5
#IBInspectable var borderColor: UIColor = UIColor.whiteColor()
private var headerView:UIView!
private var footerView:UIView!
override init() {
super.init()
createViews()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
createViews()
}
override init(frame: CGRect) {
super.init(frame: frame)
createViews()
}
func createViews() {
clipsToBounds = false
layer.masksToBounds = false
headerView = UIView()
footerView = UIView()
headerView.backgroundColor = UIColor.whiteColor()
footerView.backgroundColor = UIColor.whiteColor()
addSubview(headerView)
addSubview(footerView)
}
override func layoutSubviews() {
super.layoutSubviews()
let left = CGFloat( -borderOffsetX )
let right = CGFloat( frame.width + CGFloat(borderOffsetX*2) )
let top = CGFloat( -borderOffsetY )
let bottom = CGFloat( frame.height - CGFloat(borderWidth/2) ) + CGFloat( borderOffsetY )
headerView.frame = CGRectMake(left, top, right, CGFloat(borderWidth))
footerView.frame = CGRectMake(left, bottom, right, CGFloat(borderWidth))
}
}

Still occurring with XCode 7.3 iOS9.3, but fixed in XCode Version 8.0 beta (8S128d).

Related

Fill UIView from bottom to top

I have this UIView that right now has that static light gray filled, I need it to fill based on a number I give it that comes from an API. I tried a couple of ways but don't work for me. What's the simplest approach to this? Doesn't have to have fancy effects or anything, just a simple animation that slowly fills up the circle by giving it a number.
The API returns Int that are 0, 10, 20, 30, 40, 50, 60, 70, 80, 90 and 100.
So given those numbers I have to use them to fill the circle. They are basically percentages. So 10 should make the circle fill 10%.
This is the code that I have right now, it's in the same file as the ViewController but I don't think it's the best way, or at least it's not working because when I try to update the coeff it doesn't do it.
class BadgeView: UIView {
private let fillView = UIView(frame: CGRect.zero)
private var fillHeightConstraint: NSLayoutConstraint!
private(set) var coeff: CGFloat = 0.2 {
didSet {
updateFillViewFrame()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
public func setupView() {
layer.cornerRadius = bounds.height / 2.0
layer.masksToBounds = true
fillView.backgroundColor = UIColor(red: 220.0/255.0, green: 220.0/255.0, blue: 220.0/255.0, alpha: 0.4)
fillView.translatesAutoresizingMaskIntoConstraints = false // ensure autolayout works
addSubview(fillView)
// pin view to leading, trailing and bottom to the container view
fillView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
fillView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
fillView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
// save the constraint to be changed later
fillHeightConstraint = fillView.heightAnchor.constraint(equalToConstant: 0)
fillHeightConstraint.isActive = true
updateFillViewFrame()
}
public func updateFillViewFrame() {
fillHeightConstraint.constant = bounds.height * coeff // change the constraint value
layoutIfNeeded() // update the layout when a constraint changes
}
public func setCoeff(coeff: CGFloat, animated: Bool) {
if animated {
UIView.animate(withDuration: 0.5, animations:{ () -> Void in
self.coeff = coeff
})
} else {
self.coeff = coeff
}
}
}
The exact thing that it's not working is here:
if let ElapsedPercentual:Int = JSON.value(forKeyPath: "ResponseEntity.ElapsedPercentual") as? Int {
porcentaje = ElapsedPercentual
print(porcentaje)
>>> BadgeView().setCoeff(coeff: CGFloat(porcentaje)/100, animated: true)
That line isn't actually updating the coeff, so it's always 0.2 as previously setted. Instead it should go from 0.0 to 1.0.
First of all: I strongly suggest using a framework for build constrains programatically. Something like SnapKit.
Using a basic setup where you have a UIView (Container) that contains another UIView (myView). Where myView is used to fill the Container.
You could use the following code (didn't fully test that though) to animate the constraint and have the effect of filling up the container with the myView
self.myView.snp_makeConstraints { make in
make.left.right.bottom.equalTo(self.myContainer)
make.height.equalTo(0)
}
let newPercentage = 10
UIView.animateWithDuration(0.3) {
self.myView.snp_updateConstraints { make in
make.height.equalTo(self.myContainer.frame.height / 100 * newPercentage)
}
self.myView.superview.layoutIfNeeded()
}

Custom button is not showing correctly in Interface Builder

I'm learning to develop iOS apps in XCode.
I've created simple custom button (subclass of UIButton) that should have red background and green text. When running the app in iOS simulator, everything is ok. But in interface builder other colors (specified in interface builder) are used. Test project is available at https://www.dropbox.com/s/ro7i4omdbpwapi0/testapp%20%282%29.zip?dl=0. Can somebody help?
Here is source code of custom UIbutton class:
import UIKit
#IBDesignable class DefaultButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
func setup() {
setTitleColor(UIColor.red, for: .normal)
backgroundColor = UIColor.green
//setBackgroundImage(UIImage.createGradient(colors: [ UIColor.defaultGradientStartColor, UIColor.defaultGradientEndColor ], startPoint: CGPoint(x: 0, y: 0), endPoint: CGPoint(x: 1, y: 0), size: CGSize(width: 200, height: 200)), for: .normal)
//layer.cornerRadius = 50
}
override func awakeFromNib() {
super.awakeFromNib()
setup()
}
}
The behaviour described in you answer is correct; but i think you mixes two different things.
Basically, if you want to tint your button title, and your button background, you have to use the fields availables in interface builder. You have some nice properties to set up these things easely.
Use interface builder's parameters to design your look
So, in this case, defining a titleColor and a backgroundColor in your view setup is not relevant, since it's hardcoded.
If instead, you want to display a custom look to your button - like rounded corner - in your storyboard, you could use the #IBInspectable keyword to add new properties in interface builder.
#IBDesignable
class DefaultButton: UIButton {
#IBInspectable var borderWidth: CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
#IBInspectable var borderColor: UIColor? {
didSet {
layer.borderColor = borderColor?.cgColor
}
}
#IBInspectable var cornerRadius: CGFloat = 0 {
didSet {
layer.cornerRadius = cornerRadius
layer.masksToBounds = cornerRadius > 0
}
}
// ...
}
By doing this, you can edit and see your view with a nice render in your storyboard!
Custom properties directly in storyboard

How to use UIView in UIViewController ios

I make UIView like this. but I want to use this in UIViewController
What should I do?
import UIKit
class CardView: 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
}
}
If you want to add it in your viewController you have several options:
Add it directly in storyboard (best option):
drag a view in viewController via inspector/object library on the bottom right side in xCode
UIView object screenshot
add constraints, then tap on the view in
Constraints screenshot
and pick your custom class in identity inspector/custom class field
You can also use a view loaded from xib
Google different way to load from xib.
You can add it in code. It's a little bit harder:
Create a lazy property:
lazy var cardView: CardView = {
let cardView = CardView(frame: CGRect(x: 0, y: 0, width: 100, height: 200))
cardView.backgroundColor = .gray
cardView.layer.cornerRadius = 16.0
return cardView
}()
Add custom view in viewDidLoad for example:
override func viewDidLoad() {
super.viewDidLoad()
/* Adding subview to the VC */
view.addSubview(cardView)
}
Then add constraints. You can center it vertically/horizontally and then set custom height/width. It depends on what you want...
Check this out:
Swift | Adding constraints programmatically
I advise you to read Apple's documentation rather then just copy pasting code.
Should definitely read "documentation/UserExperience/Conceptual/AutolayoutPG/ProgrammaticallyCreatingConstraints.html" if you still haven't.
Add a new uiview to your storyboard or xib file and set its class as CardView as shown

Making UIProgressView Rounded corners

I have created a UIProgressView with following properties
progressView.progressTintColor = UIColor.appChallengeColorWithAlpha(1.0)
progressView.trackTintColor = UIColor.clearColor()
progressView.clipsToBounds = true
progressView.layer.cornerRadius = 5
I am using a UIView for border. It appears like his progress = 1, which is exactly the way I want.
But if progress value is less then 1. Corners are not rounded as it should be.
Am I missing something ? How can I make it rounded corner ?
UIProgressView has two part, progress part and track part. If you use Reveal, you can see it only has two subviews. The progress view hierarchy is very simple. so...
Objective-C
- (void)layoutSubviews
{
[super layoutSubviews];
[self.progressView.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
obj.layer.masksToBounds = YES;
obj.layer.cornerRadius = kProgressViewHeight / 2.0;
}];
}
Swift (3, 4 and 5+)
override func layoutSubviews() {
super.layoutSubviews()
subviews.forEach { subview in
subview.layer.masksToBounds = true
subview.layer.cornerRadius = kProgressViewHeight / 2.0
}
}
I admit subclass or extend progressView is the recommended way. In case of you don't want to do that for such a simple effect, this may do the trick.
Keep the situation that Apple will change the view hierarchy, and something may go wrong in mind.
Just do this in init
layer.cornerRadius = *desired_corner_radius*
clipsToBounds = true
It's very late to answer but actually I had the same problem.
Here my simplest solution (no code needed !) :
Add a container to embed your progress view
Round corner for your container (10 = height of container / 2)
The result :)
After searching and trying I decided to create my own custom progress view. Here is the code for anyone who may find them selevs in same problem.
import Foundation
import UIKit
class CustomHorizontalProgressView: UIView {
var progress: CGFloat = 0.0 {
didSet {
setProgress()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
self.backgroundColor = UIColor.clearColor()
}
override func drawRect(rect: CGRect) {
super.drawRect(rect)
setProgress()
}
func setProgress() {
var progress = self.progress
progress = progress > 1.0 ? progress / 100 : progress
self.layer.cornerRadius = CGRectGetHeight(self.frame) / 2.0
self.layer.borderColor = UIColor.grayColor().CGColor
self.layer.borderWidth = 1.0
let margin: CGFloat = 6.0
var width = (CGRectGetWidth(self.frame) - margin) * progress
let height = CGRectGetHeight(self.frame) - margin
if (width < height) {
width = height
}
let pathRef = UIBezierPath(roundedRect: CGRect(x: margin / 2.0, y: margin / 2.0, width: width, height: height), cornerRadius: height / 2.0)
UIColor.redColor().setFill()
pathRef.fill()
UIColor.clearColor().setStroke()
pathRef.stroke()
pathRef.closePath()
self.setNeedsDisplay()
}
}
Just put above code in a swift file and drag drop a UIView in IB and give it class CustomHorizontalProgressView. and That is it.
Another answer to throw in the mix, super hacky but very quick to use.
You can just grab the sublayer and set its radius. No need to write your own UIProgressView or mess with clip paths.
progressView.layer.cornerRadius = 5
progressView.layer.sublayers[1].cornerRadius = 5
progressView.subviews[1]. clipsToBounds = true
progressView.layer.masksToBounds = true
So you round the corner of your overall UIProgressView (no need for ClipsToBounds)
Then the fill bar is the 2nd sublayer, so you can grab that and round its Corners, but you also need to set the subview for that layer to clipsToBounds.
Then set the overall layer to mask to its bounds and it all looks good.
Obviously, this is massively reliant on the setup of UIProgressView not changing and the 2nd subview/layer being the fill view.
But. If you're happy with that assumption, super easy code wise to use.
Basically progress view's (Default Style) subviews consist of 2 image view.
One for the "progress", and one for the "track".
You can loop the subviews of progress view, and set the each of image view's corner radius.
for let view: UIView in self.progressView.subviews {
if view is UIImageView {
view.clipsToBounds = true
view.layer.cornerRadius = 15
}
}
Yes ,one thing is missed...corner radius is set to progressview and it is reflecting as expected..
But if you want your track image to be rounded you have to customise your progressview.
You have to use image with rounded corner.
[progressView setTrackImage:[UIImage imageNamed:#"roundedTrack.png"]];
//roundedTrack.png must be of rounded corner
This above code will help you to change image of trackView for your progressview.
You may face the inappropriate stretching of image. You have to make your image resizable.
May be the link below will be useful if issue arise
https://www.natashatherobot.com/ios-stretchable-button-uiedgeinsetsmake/
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let v = ProgessView(frame: CGRect(x: 20, y: 200, width: 100, height: 10))
view.addSubview(v)
//v.progressLayer.strokeEnd = 0.8
}
}
class ProgessView: UIView {
lazy var progressLayer: CAShapeLayer = {
let line = CAShapeLayer()
let path = UIBezierPath()
path.move(to: CGPoint(x: 5, y: 5))
path.addLine(to: CGPoint(x: self.bounds.width - 5, y: 5))
line.path = path.cgPath
line.lineWidth = 6
line.strokeColor = UIColor(colorLiteralRed: 127/255, green: 75/255, blue: 247/255, alpha: 1).cgColor
line.strokeStart = 0
line.strokeEnd = 0.5
line.lineCap = kCALineCapRound
line.frame = self.bounds
return line
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white
layer.cornerRadius = 5
layer.borderWidth = 1
layer.borderColor = UIColor(colorLiteralRed: 197/255, green: 197/255, blue: 197/255, alpha: 1).cgColor
layer.addSublayer(progressLayer)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Test my codes. You can design the height and the width as your want. You can use strokeEnd to change the progress of the progressView. You can add an animation to it. But actually, it is already animatable, you can change the value of the strokeEnd to see its primary effect. If you want to design your own animation. Try CATransaction like below.
func updateProgress(_ progress: CGFloat) {
CATransaction.begin()
CATransaction.setAnimationDuration(3)
progressLayer.strokeEnd = progress
CATransaction.commit()
}
I had this exact same problem, which is what led me to your question after googling like crazy. The problem is two-fold. First, how to make the inside of the progress bar round at the end (which 季亨达's answer shows how to do), and secondly, how to make the round end of the CAShapeLayer you added match up with the square end of the original progress bar underneath (the answer to this other StackOverflow question helped with that How to get the exact point of objects in swift?) If you replace this line of code in 季亨达's answer:
path.addLine(to: CGPoint(x: self.bounds.width - 5, y: 5))
with this:
path.addLine(to: CGPoint(x: (Int(self.progress * Float(self.bounds.width))), y: 5))
you will hopefully get the result you're looking for.
With swift 4.0 I'm doing in this way:
let progressViewHeight: CGFloat = 4.0
// Set progress view height
let transformScale = CGAffineTransform(scaleX: 1.0, y: progressViewHeight)
self.progressView.transform = transformScale
// Set progress round corners
self.progressView.layer.cornerRadius = progressViewHeight
self.progressView.clipsToBounds = true
//Updated for swift 4
import Foundation
import UIKit
class CustomHorizontalProgressView: UIView {
var progress: CGFloat = 0.0 {
didSet {
setProgress()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
self.backgroundColor = UIColor.clear
}
override func draw(_ rect: CGRect) {
super.draw(rect)
setProgress()
}
func setProgress() {
var progress = self.progress
progress = progress > 1.0 ? progress / 100 : progress
self.layer.cornerRadius = self.frame.height / 2.0
self.layer.borderColor = UIColor.gray.cgColor
self.layer.borderWidth = 1.0
let margin: CGFloat = 6.0
var width = (self.frame.width - margin) * progress
let height = self.frame.height - margin
if (width < height) {
width = height
}
let pathRef = UIBezierPath(roundedRect: CGRect(x: margin / 2.0, y: margin / 2.0, width: width, height: height), cornerRadius: height / 2.0)
UIColor.red.setFill()
pathRef.fill()
UIColor.clear.setStroke()
pathRef.stroke()
pathRef.close()
self.setNeedsDisplay()
}
}
Swift 4.2 version from Umair Afzal's solution
class CustomHorizontalProgressView: UIView {
var strokeColor: UIColor?
var progress: CGFloat = 0.0 {
didSet {
setNeedsDisplay()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
self.backgroundColor = UIColor.clear
}
override func draw(_ rect: CGRect) {
super.draw(rect)
setProgress()
}
func setProgress() {
var progress = self.progress
progress = progress > 1.0 ? progress / 100 : progress
self.layer.cornerRadius = frame.size.height / 2.0
self.layer.borderColor = UIColor.gray.cgColor
self.layer.borderWidth = 1.0
let margin: CGFloat = 6.0
var width = (frame.size.width - margin) * progress
let height = frame.size.height - margin
if (width < height) {
width = height
}
let pathRef = UIBezierPath(roundedRect: CGRect(x: margin / 2.0, y: margin / 2.0, width: width, height: height), cornerRadius: height / 2.0)
strokeColor?.setFill()
pathRef.fill()
UIColor.clear.setStroke()
pathRef.stroke()
pathRef.close()
}
}
And to use it
var progressView: CustomHorizontalProgressView = {
let view = CustomHorizontalProgressView()
view.strokeColor = UIColor.orange
view.progress = 0.5
return view
}()
Set line cap :
.lineCap = kCALineCapRound;

ContainerView with shadow and rounded edges

I would like to create custom ContainerView with shadowed and rounded edges. This ContainerView is in form of small rectangle placed on the top of another UIView. In this peculiar situation neither additional layers nor drawing shadow using CoreGraphics are helpful.
You're wrong that additional views/layers won't help.
You can place roundedContainer with rounded corners into another shadowedView with shadow added to it's layer.
To avoid those white corners make sure you set background color to clear somewhere.
Example:
//superview for container with rounded corners
shadowedView.backgroundColor = UIColor.clear //this will fix your white corners issue
shadowedView.layer.shadowColor = UIColor.black.cgColor
shadowedView.layer.shadowOffset = .zero
shadowedView.layer.shadowOpacity = 0.3
shadowedView.layer.shadowRadius = 5.0
//add a container with rounded corners
let roundedView = UIView()
roundedView.frame = baseView.bounds
roundedView.layer.cornerRadius = 10
roundedView.layer.masksToBounds = true
shadowedView.addSubview(roundedView)
I found a proper solution. I dropped shadow to ContainerView which is a superclass for every UIView inside. Then, I rounded edges using UIViewController class for this small rectangle area.
class GraphViewController: UIViewController {
#IBOutlet var graphView: GraphViewRenderer!
override func viewDidLoad() {
graphView.layer.cornerRadius = 20.0
graphView.layer.masksToBounds = true
super.viewDidLoad()
}
}
class GraphContainerView: UIView {
func applyPlainShadow() {
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize.zero
layer.shadowRadius = 5.0
layer.shadowOpacity = 0.7
}
override init(frame: CGRect) {
super.init(frame: frame)
applyPlainShadow()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
applyPlainShadow()
}
}

Resources