How to set topAnchor constraint relative to the superview height - objective-c

I have two views: toastView and view. toastView is subview of view.
I want to position toastView on the y axis by 80% of view height.
How can I do this using constants in the code?
I assumed that there is a method like:
[toastView.topAnchor constraintEqualToAnchor:view.heightAnchor multiplier:0.8].active = YES;
but i can't mixing NSLayoutDimension (width and height) and NSLayoutYAxisAnchor (X and Y)
This is how it looks in the design:

The trick here is to set the top of toastView equal to the bottom of self.view with a multiplier of 0.8:
Objective-C:
[NSLayoutConstraint constraintWithItem: toastView attribute: NSLayoutAttributeTop
relatedBy: NSLayoutRelationEqual toItem: self.view
attribute: NSLayoutAttributeBottom multiplier: 0.8 constant: 0].active = YES;
Swift:
NSLayoutConstraint(item: toastView, attribute: .top, relatedBy: .equal,
toItem: self.view, attribute: .bottom, multiplier: 0.8, constant: 0).isActive = true

You can do it like this in swift
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let yConstraint = NSLayoutConstraint(item: toastView, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: view.bounds.height * 0.8 )
NSLayoutConstraint.activateConstraints([yConstraint])
}
Objective c
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
NSLayoutConstraint *topAnchorConstraint = [NSLayoutConstraint constraintWithItem:toastView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeTop multiplier:1 constant:(view.bounds.size.height * 0.8)];
[self.view addConstraint:topAnchorConstraint];
}

Related

Stuck with tvOS and Focus Guides

I'm really stuck with this issue. I'm trying to add a button on top of an AVPlayerViewController that says "Skip Intro" (similar to the button that Netflix has on their content).
I've added 4 or 5 buttons horizontally starting just above the left side of the seek bar and each button are spaced about 10 points apart. I only need 1 button, but at this point I cannot figure out why I can't click any of these buttons at all:
I've declared the button and the focus guide variables as private:
Here's the viewDidLoad method:
- (void)viewDidLoad
{
[super viewDidLoad];
self.avPlayerViewController = [[AVPlayerViewController alloc] init];
[self addChildViewController:self.avPlayerViewController];
[self.view addSubview:self.avPlayerViewController.view];
self.avPlayerViewController.showsPlaybackControls = YES;
self.avPlayerViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:self.avPlayerViewController.view
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:0];
NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:self.avPlayerViewController.view
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:0.0];
NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:self.avPlayerViewController.view
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0];
NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:self.avPlayerViewController.view
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:0.0];
[self.view addConstraint:height];
[self.view addConstraint:width];
[self.view addConstraint:top];
[self.view addConstraint:leading];
[self.avPlayerViewController didMoveToParentViewController:self];
[self setupBreakButtonWithWidth:225 height:50 xCoordinate:100 yCoordinate:850];
[self setupBreakButtonWithWidth:225 height:50 xCoordinate:335 yCoordinate:850];
[self setupBreakButtonWithWidth:225 height:50 xCoordinate:570 yCoordinate:850];
[self setupBreakButtonWithWidth:225 height:50 xCoordinate:805 yCoordinate:850];
[self setupFocusGuide];
[self createBannerAdView];
[self resetUpNextState];
}
As you can see, I'm just adding buttons to the bottom of the screen (above the seek bar) and then I setup the focus guide and the preferredFocusEnvironments:
- (NSArray<id<UIFocusEnvironment>> *)preferredFocusEnvironments {
NSLog(#"**** BMNativeVideoPlayerViewController -> preferredFocusEnvironments");
return #[self.skipBreakButton];
}
- (void)setupFocusGuide
{
NSLog(#"**** BMNativeVideoPlayerViewController -> setupFocusGuide");
// allocate focus guide and add it to the view
self.focusGuide = [[UIFocusGuide alloc] init];
[self.view addLayoutGuide:self.focusGuide];
// define constraints
[self.focusGuide.leftAnchor constraintEqualToAnchor:self.view.leftAnchor].active = YES;
[self.focusGuide.rightAnchor constraintEqualToAnchor:self.view.rightAnchor].active = YES;
[self.focusGuide.topAnchor constraintEqualToAnchor:_skipBreakButton.topAnchor].active = YES;
[self.focusGuide.bottomAnchor constraintEqualToAnchor:_skipBreakButton.bottomAnchor].active = YES;
// select the default focusable view
self.focusGuide.preferredFocusEnvironments = #[self.skipBreakButton];
}
Here is the method where the button is configured, along with the button handler method and the didUpdateFocusInContext method (although I don't know if it's doing what it's supposed to):
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
{
self.focusGuide.preferredFocusEnvironments = #[self.skipBreakButton];
}
- (void)setupBreakButtonWithWidth:(CGFloat)width height:(CGFloat)height xCoordinate:(CGFloat)x yCoordinate:(CGFloat)y
{
NSLog(#"**** BMNativeVideoPlayerViewController -> setupBreakButton");
_skipBreakButton = [[UIButton alloc] init];
_skipBreakButton.backgroundColor = [UIColor whiteColor];
[self.skipBreakButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
_skipBreakButton.alpha = 1.0f;
_skipBreakButton.layer.cornerRadius = 3;
_skipBreakButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.avPlayerViewController.contentOverlayView addSubview:_skipBreakButton];
[self.avPlayerViewController.contentOverlayView bringSubviewToFront:_skipBreakButton];
NSLayoutConstraint *xConstraint = [NSLayoutConstraint
constraintWithItem:_skipBreakButton attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual toItem:self.view attribute:
NSLayoutAttributeLeading multiplier:1.0 constant:x];
NSLayoutConstraint *yConstraint = [NSLayoutConstraint
constraintWithItem:_skipBreakButton attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual toItem:self.view attribute:
NSLayoutAttributeTop multiplier:1.0f constant:y];
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:_skipBreakButton
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:width];
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:_skipBreakButton
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:height];
[self.view addConstraints:#[xConstraint, yConstraint, widthConstraint, heightConstraint]];
[self.skipBreakButton setUserInteractionEnabled:YES];
[self.skipBreakButton setTitle:#"Skip Intro" forState:UIControlStateNormal];
[self.skipBreakButton.titleLabel setFont:[UIFont fontWithName:#"Helvetica-Bold" size:26.0]];
NSLog(#"**** Can the skip break button be focused? %d", self.skipBreakButton.canBecomeFocused);
[self.skipBreakButton addTarget:self action:#selector(skipBreakButtonClicked:) forControlEvents:UIControlEventPrimaryActionTriggered];
}
- (void)skipBreakButtonClicked:(UIButton *) sender {
NSLog(#"**** BMNativeVideoPlayerViewController -> skipBreakButtonClicked");
}
I cannot click on any of these buttons. I need help to figure out how to get it to work.
It seems that a view is on top of this button but I'm not sure how to fix it:
(lldb) po [UIFocusDebugger checkFocusabilityForItem: 0x7ff86fe60510]
The following issues were found that would prevent this item from being focusable:
- ISSUE: One or more ancestors have issues that may be preventing this item from being focusable. Details:
<_AVPlayerViewControllerContainerView 0x7ff86fc29eb0>:
- ISSUE: This view returns YES from -canBecomeFocused, which will prevent its subviews from being focusable.
Please advise?
AVPlayer view controller steals all of the view inputs when it is full screen. In my experience what you have to do is keep the player 1 pixel smaller than the full screen when you have your custom elements and then when your custom elements aren't on the screen you can make it full size. If you set breakpoints in your focus override code you will see that it doesn't hit and this is because Apple is controlling the inputs on their AVPlayerViewController.
If you try the above it should resolve the issue.
You could achieve this behaviour by presenting a modal view controller (with a skip button on it) as overlay over the context of the player.
You can then dismiss the modal view controller after a few seconds (as done in the example below), or programatically if you know when the intro actually finishes.
import UIKit
import AVFoundation
import AVKit
class ThePlayerViewController: AVPlayerViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
play(stream: URL(string: "https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_ts/master.m3u8")!)
presentSkipOverlay()
}
// MARK: - Private
private func play(stream: URL) {
let asset = AVAsset(url: stream)
let playetItem = AVPlayerItem(asset: asset)
player = AVPlayer(playerItem: playetItem)
player?.play()
}
private func presentSkipOverlay() {
let skipOverlayViewController = SkipOverlayViewController()
skipOverlayViewController.onSkip = {
[weak self] in
// Skip the intro here
self?.player?.seek(to: CMTime(seconds: 60.0, preferredTimescale: 1))
}
skipOverlayViewController.modalPresentationStyle = .overCurrentContext
skipOverlayViewController.accessibilityViewIsModal = true
present(skipOverlayViewController, animated: true, completion: {
// Dismiss the overlay automatically after 3 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
skipOverlayViewController.dismiss(animated: true, completion: nil)
}
})
}
}
And this would be a naive implementation of the Overlay view controller:
final class SkipOverlayViewController: UIViewController {
var onSkip: (() -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
setUpView()
}
// MARK: - Private
private lazy var skipButton: UIButton = {
let skipButton = UIButton()
skipButton.backgroundColor = .white
skipButton.setTitleColor(.black, for: .normal)
skipButton.setTitle("Skip Intro", for: .normal)
skipButton.addTarget(self, action: #selector(skipButtonWasPressed), for: .primaryActionTriggered)
return skipButton
}()
private func setUpView() {
view.addSubview(skipButton)
skipButton.translatesAutoresizingMaskIntoConstraints = false
skipButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
skipButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -200).isActive = true
skipButton.widthAnchor.constraint(equalToConstant: 225).isActive = true
}
// MARK: - Actions
#objc
func skipButtonWasPressed() {
print("Skip button was Pressed")
onSkip?()
}
}
And this is the result:

Apply and animate many constraints to outlet collection of uiviews

I have an outlet collection of labels. The labels are in stack views parented by a stack view. When the view loads I'd like to have each label fade in and move slightly to the right one after the other. I can apply the constraint in a loop to offset it. But only one will animate back to the final position.
-(void)viewDidLoad {
[super viewDidLoad];
for (UILabel *lbl in _constructionlabels) {
lbl.alpha = 0.0;
leadingCnst=[NSLayoutConstraint
constraintWithItem:lbl
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:[lbl superview]
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:-25];
[self.view addConstraint:leadingCnst];
}
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
leadingCnst.constant = 0;
[UIView animateWithDuration:0.33 delay:2 options:UIViewAnimationOptionCurveEaseOut animations:^{
for (UILabel *lbl in self->_constructionlabels) {
lbl.alpha = 1.0;
}
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
}];
}
How can I apply constraints to every needed label, and then animate all of them one after the other?
Keep references to each label constraint and start all animations at once, each with a delay.
// Declare array to hold references to constraints
NSMutableArray* _labelConstraints = [NSMutableArray array];
-(void) viewDidLoad {
[super viewDidLoad];
for (UILabel * lbl in _constructionlabels) {
lbl.alpha = 0.0;
NSLayoutConstraint* leadingCnst = [NSLayoutConstraint
constraintWithItem: lbl
attribute: NSLayoutAttributeLeading
relatedBy: NSLayoutRelationEqual
toItem: [lbl superview]
attribute: NSLayoutAttributeLeading
multiplier: 1.0
constant: -25
];
[self.view addConstraint: leadingCnst];
// Add constraint reference
[_labelConstraints addObject: #(leadingCnst)];
}
}
-(void) viewDidAppear: (BOOL) animated {
[super viewDidAppear: animated];
for (i = 0; i < [_constructionlabels count]; i++) {
// Get label
Label* lbl = [_constructionlabels objectAtIndex:i];
// Get constraint
NSLayoutConstraint* labelConstraint = [_labelConstraints objectAtIndex:i];
// Animate
[UIView animateWithDuration: 0.33 delay: i options: UIViewAnimationOptionCurveEaseOut animations: ^ {
lbl.alpha = 1.0;
labelConstraint.constant = 0;
[self.view layoutIfNeeded];
}
completion: ^ (BOOL finished) {}
];
}
}
Note: This is just a proof of concept - you may want to refactor the code.
(It's been a while since I wrote ObjC, if you let me know any mistakes I'll correct them.)
You will need to put a recursive function to animate each label one by one without for loop. Add completion block in the animation.
func animate(_ label: UILabel, completion: #escaping () -> Void) {
UIView.animate(withDuration: 1, animations: {
// add animation here
}, completion: { _ in
completion()
})
}
let labelCollection = [UILabel]()
var index = 0
func startAnimation() {
animate(labelCollection[index], completion: {
if self.index < self.labelCollection.count - 1 {
self.index = self.index + 1
self.startAnimation()
}
})
}

UIBezierPath arc and rectangle problem on iPhone X family but not on earlier phones

I have created a Objective-C category for UIView to draw rounded corners. I am trying to implement a Pill button and the function is working except on iPhone X family. Because the Arc is at the end of the UIView, I think I have to draw the rest of the view as a rectangle to fill in. If there is a better way to do this I would love to know. Here is the function.
- (void)setRounded:(BOOL)clockwise borderColor:(UIColor *)borderColor {
CGRect rect = self.bounds;
CGFloat offset = CGRectGetMidY(rect);
NSAssert(rect.size.width-rect.origin.x >= offset, #"width too small %f < %f", rect.size.width, offset);
UIBezierPath *maskPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(clockwise?rect.size.width-rect.origin.x-offset:rect.origin.x+offset, offset) radius:offset startAngle:GLKMathDegreesToRadians(270) endAngle:GLKMathDegreesToRadians(90) clockwise:clockwise];
DLog(#"(clockwise: %#) maskPath: %#", clockwise?#"true":#"false", NSStringFromCGRect([maskPath bounds]));
[maskPath setUsesEvenOddFillRule:NO];
UIBezierPath *squarePath = [UIBezierPath bezierPath];
if (clockwise) {
[squarePath moveToPoint:CGPointMake(rect.origin.x, rect.origin.y)];
[squarePath addLineToPoint:CGPointMake(rect.size.width-rect.origin.x-offset, rect.origin.y)];
[squarePath addLineToPoint:CGPointMake(rect.size.width-rect.origin.x-offset, rect.origin.y+rect.size.height)];
[squarePath addLineToPoint:CGPointMake(rect.origin.x, rect.origin.y+rect.size.height)];
[squarePath addLineToPoint:CGPointMake(rect.origin.x, rect.origin.y)];
} else {
[squarePath moveToPoint:CGPointMake(rect.origin.x+offset, rect.origin.y)];
[squarePath addLineToPoint:CGPointMake(rect.size.width-rect.origin.x, rect.origin.y)];
[squarePath addLineToPoint:CGPointMake(rect.size.width-rect.origin.x, round(rect.origin.y+rect.size.height))];
[squarePath addLineToPoint:CGPointMake(rect.origin.x+offset, round(rect.origin.y+rect.size.height))];
//[squarePath addLineToPoint:CGPointMake(rect.origin.x+offset, rect.origin.y)];
[squarePath closePath];
}
DLog(#"squarePath: %#", NSStringFromCGRect([squarePath bounds]));
[maskPath appendPath:squarePath];
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = rect;
maskLayer.path = maskPath.CGPath;
self.layer.mask = maskLayer;
CAShapeLayer *shape = [CAShapeLayer layer];
shape.frame = self.frame;
shape.path = maskPath.CGPath;
shape.lineWidth = 1.0;
shape.strokeColor = borderColor.CGColor;
shape.fillColor = borderColor.CGColor;
[self.layer addSublayer:shape];
}
What I would do is just draw the entire pill shape as a single path, and fill it. Here is a UIView subclass that draws itself as a pill shape:
So you see it's just a matter of setting the size and proportions of that view the way you want them, and the pill will just appear, no matter what device you're running on.
Here's the code for the pill view:
class PillView : UIView {
required init?(coder aDecoder: NSCoder) {
super.init(coder:aDecoder)
self.isOpaque = false
}
override func draw(_ rect: CGRect) {
let ins : CGFloat = 2
let r = self.bounds.insetBy(dx: ins, dy: ins)
let radius : CGFloat = r.size.height / 2
let d90 = CGFloat.pi/2
let path = CGMutablePath()
path.move(to:CGPoint(x:r.maxX - radius, y:ins))
path.addArc(center:CGPoint(x:radius+ins, y:radius+ins), radius: radius,
startAngle: -d90, endAngle: d90, clockwise: true)
path.addArc(center:CGPoint(x:r.maxX - radius, y:radius+ins), radius: radius,
startAngle: d90, endAngle: -d90, clockwise: true)
path.closeSubpath()
let bez = UIBezierPath()
bez.cgPath = path
UIColor.green.setFill()
bez.fill()
}
}

Circular Progress Bars in IOS

I want to create a circular progress bar like the following:
How can I do that using Objective-C and Cocoa?
How I started doing it was creating a UIView and editing the drawRect, but I am bit lost. Any help would be greatly appreciated.
Thanks!
The basic concept is to use the UIBezierPath class to your advantage. You are able to draw arcs, which achieve the effect you're after. I've only had half an hour or so to have a crack at this, but my attempt is below.
Very rudimentary, it simply uses a stroke on the path, but here we go. You can alter/modify this to your exact needs, but the logic to do the arc countdown will be very similar.
In the view class:
#interface TestView () {
CGFloat startAngle;
CGFloat endAngle;
}
#end
#implementation TestView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor whiteColor];
// Determine our start and stop angles for the arc (in radians)
startAngle = M_PI * 1.5;
endAngle = startAngle + (M_PI * 2);
}
return self;
}
- (void)drawRect:(CGRect)rect
{
// Display our percentage as a string
NSString* textContent = [NSString stringWithFormat:#"%d", self.percent];
UIBezierPath* bezierPath = [UIBezierPath bezierPath];
// Create our arc, with the correct angles
[bezierPath addArcWithCenter:CGPointMake(rect.size.width / 2, rect.size.height / 2)
radius:130
startAngle:startAngle
endAngle:(endAngle - startAngle) * (_percent / 100.0) + startAngle
clockwise:YES];
// Set the display for the path, and stroke it
bezierPath.lineWidth = 20;
[[UIColor redColor] setStroke];
[bezierPath stroke];
// Text Drawing
CGRect textRect = CGRectMake((rect.size.width / 2.0) - 71/2.0, (rect.size.height / 2.0) - 45/2.0, 71, 45);
[[UIColor blackColor] setFill];
[textContent drawInRect: textRect withFont: [UIFont fontWithName: #"Helvetica-Bold" size: 42.5] lineBreakMode: NSLineBreakByWordWrapping alignment: NSTextAlignmentCenter];
}
For the view controller:
#interface ViewController () {
TestView* m_testView;
NSTimer* m_timer;
}
#end
- (void)viewDidLoad
{
// Init our view
[super viewDidLoad];
m_testView = [[TestView alloc] initWithFrame:self.view.bounds];
m_testView.percent = 100;
[self.view addSubview:m_testView];
}
- (void)viewDidAppear:(BOOL)animated
{
// Kick off a timer to count it down
m_timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(decrementSpin) userInfo:nil repeats:YES];
}
- (void)decrementSpin
{
// If we can decrement our percentage, do so, and redraw the view
if (m_testView.percent > 0) {
m_testView.percent = m_testView.percent - 1;
[m_testView setNeedsDisplay];
}
else {
[m_timer invalidate];
m_timer = nil;
}
}
My example with magic numbers (for better understanding):
CAShapeLayer *circle = [CAShapeLayer layer];
circle.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(29, 29) radius:27 startAngle:-M_PI_2 endAngle:2 * M_PI - M_PI_2 clockwise:YES].CGPath;
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = [UIColor greenColor].CGColor;
circle.lineWidth = 4;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
animation.duration = 10;
animation.removedOnCompletion = NO;
animation.fromValue = #(0);
animation.toValue = #(1);
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
[circle addAnimation:animation forKey:#"drawCircleAnimation"];
[imageCircle.layer.sublayers makeObjectsPerformSelector:#selector(removeFromSuperlayer)];
[imageCircle.layer addSublayer:circle];
I have implemented a simple library for iOS doing just that. It's based on the UILabel class so you can display whatever you want inside your progress bar, but you can also leave it empty.
Once initialized, you only have one line of code to set the progress :
[_myProgressLabel setProgress:(50/100))];
The library is named KAProgressLabel
You can check out my lib MBCircularProgressBar
For Swift use this,
let circle = UIView(frame: CGRectMake(0,0, 100, 100))
circle.layoutIfNeeded()
let centerPoint = CGPoint (x: circle.bounds.width / 2, y: circle.bounds.width / 2)
let circleRadius : CGFloat = circle.bounds.width / 2 * 0.83
var circlePath = UIBezierPath(arcCenter: centerPoint, radius: circleRadius, startAngle: CGFloat(-0.5 * M_PI), endAngle: CGFloat(1.5 * M_PI), clockwise: true )
let progressCircle = CAShapeLayer()
progressCircle.path = circlePath.CGPath
progressCircle.strokeColor = UIColor.greenColor().CGColor
progressCircle.fillColor = UIColor.clearColor().CGColor
progressCircle.lineWidth = 1.5
progressCircle.strokeStart = 0
progressCircle.strokeEnd = 0.22
circle.layer.addSublayer(progressCircle)
self.view.addSubview(circle)
Reference: See Here.
Swift 3 use this,
CAShapeLayer with Animation : Continue with Zaid Pathan ans.
let circle = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
circle.layoutIfNeeded()
var progressCircle = CAShapeLayer()
let centerPoint = CGPoint (x: circle.bounds.width / 2, y: circle.bounds.width / 2)
let circleRadius : CGFloat = circle.bounds.width / 2 * 0.83
let circlePath = UIBezierPath(arcCenter: centerPoint, radius: circleRadius, startAngle: CGFloat(-0.5 * M_PI), endAngle: CGFloat(1.5 * M_PI), clockwise: true )
progressCircle = CAShapeLayer ()
progressCircle.path = circlePath.cgPath
progressCircle.strokeColor = UIColor.green.cgColor
progressCircle.fillColor = UIColor.clear.cgColor
progressCircle.lineWidth = 2.5
progressCircle.strokeStart = 0
progressCircle.strokeEnd = 1.0
circle.layer.addSublayer(progressCircle)
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.toValue = 1.0
animation.duration = 5.0
animation.fillMode = kCAFillModeForwards
animation.isRemovedOnCompletion = false
progressCircle.add(animation, forKey: "ani")
self.view.addSubview(circle)
Here a Swift example of how to make a simple, not closed(to leave space for long numbers) circular progress bar with rounded corners and animation.
open_circular_progress_bar.jpg
func drawBackRingFittingInsideView(lineWidth: CGFloat, lineColor: UIColor) {
let halfSize:CGFloat = min( bounds.size.width/2, bounds.size.height/2)
let desiredLineWidth:CGFloat = lineWidth
let circle = CGFloat(Double.pi * 2)
let startAngle = CGFloat(circle * 0.1)
let endAngle = circle – startAngle
let circlePath = UIBezierPath(
arcCenter: CGPoint(x:halfSize, y:halfSize),
radius: CGFloat( halfSize – (desiredLineWidth/2) ),
startAngle: startAngle,
endAngle: endAngle,
clockwise: true)
let shapeBackLayer = CAShapeLayer()
shapeBackLayer.path = circlePath.cgPath
shapeBackLayer.fillColor = UIColor.clear.cgColor
shapeBackLayer.strokeColor = lineColor.cgColor
shapeBackLayer.lineWidth = desiredLineWidth
shapeBackLayer.lineCap = .round
layer.addSublayer(shapeBackLayer)
}
And the animation function.
func animateCircle(duration: TimeInterval) {
let animation = CABasicAnimation(keyPath: “strokeEnd”)
animation.duration = duration
animation.fromValue = 0
animation.toValue = 1
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
shapeLayer.strokeEnd = 1.0
shapeLayer.add(animation, forKey: “animateCircle”)
}
There is a good blog with examples.

IOS: create a UIImage or UIImageView with rounded corners

Is it possible create an UIImage or an UIImageView with rounded corners? Because I want take an UIImage and show it inside an UIImageView, but I don't know how to do it.
Yes, it is possible.
Import the QuartzCore (#import <QuartzCore/QuartzCore.h>) header and play with the layer property of the UIImageView.
yourImageView.layer.cornerRadius = yourRadius;
yourImageView.clipsToBounds = YES;
See the CALayer class reference for more info.
Try this Code For Round Image Import QuartzCore framework
simple way to create Round Image
imageView.layer.backgroundColor=[[UIColor clearColor] CGColor];
imageView.layer.cornerRadius=20;
imageView.layer.borderWidth=2.0;
imageView.layer.masksToBounds = YES;
imageView.layer.borderColor=[[UIColor redColor] CGColor];
Objective-C
-(UIImage *)makeRoundedImage:(UIImage *) image
radius: (float) radius;
{
CALayer *imageLayer = [CALayer layer];
imageLayer.frame = CGRectMake(0, 0, image.size.width, image.size.height);
imageLayer.contents = (id) image.CGImage;
imageLayer.masksToBounds = YES;
imageLayer.cornerRadius = radius;
UIGraphicsBeginImageContext(image.size);
[imageLayer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *roundedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return roundedImage;
}
Swift 3
func makeRoundedImage(image: UIImage, radius: Float) -> UIImage {
var imageLayer = CALayer()
imageLayer.frame = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
imageLayer.contents = image.cgImage
imageLayer.masksToBounds = true
imageLayer.cornerRadius = radius
UIGraphicsBeginImageContext(image.size)
imageLayer.render(in: UIGraphicsGetCurrentContext())
var roundedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return roundedImage
}
uiimageview.layer.cornerRadius = uiimageview.frame.size.height/2;
uiimageview.clipToBounds = YES;
#import <QuartzCore/QuartzCore.h>
// UIImageView+OSExt.h
#import <UIKit/UIKit.h>
#interface UIImageView (OSExt)
- (void)setBorder:(CGFloat)borderWidth color:(UIColor*)color;
#end
// UIImageView+OSExt.m
#import "UIImageView+OSExt.h"
#implementation UIImageView (OSExt)
- (void)layoutSublayersOfLayer:(CALayer *)layer
{
for ( CALayer *sub in layer.sublayers )
{
if ( YES == [sub.name isEqual:#"border-shape"])
{
CGFloat borderHalf = floor([(CAShapeLayer*)sub lineWidth] * .5);
sub.frame = layer.bounds;
[sub setBounds:CGRectInset(layer.bounds, borderHalf, borderHalf)];
[sub setPosition:CGPointMake(CGRectGetMidX(layer.bounds),
CGRectGetMidY(layer.bounds))];
}
}
}
- (void)setBorder:(CGFloat)borderWidth color:(UIColor*)color
{
assert(self.frame.size.width == self.frame.size.height);
for ( CALayer *sub in [NSArray arrayWithArray:self.layer.sublayers] )
{
if ( YES == [sub.name isEqual:#"border-shape"])
{
[sub removeFromSuperlayer];
break;
}
}
CGFloat borderHalf = floor(borderWidth * .5);
self.layer.cornerRadius = self.layer.bounds.size.width * .5;
CAShapeLayer *circleLayer = [CAShapeLayer layer];
self.layer.delegate = (id<CALayerDelegate>)self;
circleLayer.name = #"border-shape";
[circleLayer setBounds:CGRectInset(self.bounds, borderHalf, borderHalf)];
[circleLayer setPosition:CGPointMake(CGRectGetMidX(self.layer.bounds),
CGRectGetMidY(self.layer.bounds))];
[circleLayer setPath:[[UIBezierPath bezierPathWithOvalInRect:circleLayer.bounds] CGPath]];
[circleLayer setStrokeColor:color.CGColor];
[circleLayer setFillColor:[UIColor clearColor].CGColor];
[circleLayer setLineWidth:borderWidth];
{
circleLayer.shadowOffset = CGSizeZero;
circleLayer.shadowColor = [[UIColor whiteColor] CGColor];
circleLayer.shadowRadius = borderWidth;
circleLayer.shadowOpacity = .9f;
circleLayer.shadowOffset = CGSizeZero;
}
// Add the sublayer to the image view's layer tree
[self.layer addSublayer:circleLayer];
// old variant
//CALayer *layer = self.layer;
//layer.masksToBounds = YES;
//layer.cornerRadius = self.frame.size.width * 0.5;
//layer.borderWidth = borderWidth;
//layer.borderColor = color;
}
#end
Setting cornerRadius and clipsToBounds is the right way to do this. However if the view's size changes, the radius will not update. In order to get proper resizing and animation behavior, you need to create a UIImageView subclass.
class RoundImageView: UIImageView {
override var bounds: CGRect {
get {
return super.bounds
}
set {
super.bounds = newValue
setNeedsLayout()
}
}
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = bounds.width / 2.0
clipsToBounds = true
}
}
Try this to get rounded corners of the image View and also to colour the corners:
imageView.layer.cornerRadius = imageView.frame.size.height/2;
imageView.layer.masksToBounds = YES;
imageView.layer.borderColor = [UIColor colorWithRed:148/255. green:79/255. blue:216/255. alpha:1.0].CGColor;
imageView.layer.borderWidth=2;
Condition*: The height and the width of the imageView must be same to get rounded corners.
layer.cornerRadius = imageviewHeight/2
layer.masksToBounds = true
It is possible but I'll advice you to create transparent png image (mask) with round corners and place it over you image with UIImageView. It might be quicker solution (for example if you need animations or scrolling).
Here how i set my rounded avatar at the center of it contain view:
-(void)setRoundedAvatar:(UIImageView *)avatarView toDiameter:(float)newSize atView:(UIView *)containedView;
{
avatarView.layer.cornerRadius = newSize/2;
avatarView.clipsToBounds = YES;
avatarView.frame = CGRectMake(0, 0, newSize, newSize);
CGPoint centerValue = CGPointMake(containView.frame.size.width/2, containedView.frame.size.height/2);
avatarView.center = centerValue;
}
Circle with UIBeizerPath #Swift-3 && #imageExtension
class ViewController: UIViewController {
#IBOutlet weak var imageOutlet: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let image = UIImage(named: "IMG_0001.JPG")
if let image = image {
let renderimage = image.imageCroppingBezierPath(path: UIBezierPath(arcCenter: CGPoint(x:image.size.width/2,y:image.size.width/2 ) , radius: 200, startAngle: 0, endAngle: (2 * CGFloat(M_PI) ), clockwise: true) )
imageOutlet.image = renderimage
}
}
}
extension UIImage {
func imageCroppingBezierPath(path:UIBezierPath) ->UIImage {
let frame = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)
//Defining a graphic context to paint on
UIGraphicsBeginImageContextWithOptions(self.size, false, 0.0)
//Get the current graphics context (if it exists)
let context = UIGraphicsGetCurrentContext()
//save the current graphic context
context?.saveGState()
// clipping area
path.addClip()
self.draw(in: frame)
//To extract an image from our canvas
let image = UIGraphicsGetImageFromCurrentImageContext()
//restore graphic context
context?.restoreGState()
//remove current context from stack
UIGraphicsEndImageContext()
return image!
}
}
# import QuartzCore framework
imageView.layer.cornerRadius=imgvwUser.frame.size.width/2;
imageView.layer.masksToBounds = YES;
The height and the width of the imageView must be same to get rounded corners.