Center a view inside of an UITableViewCell? Looks centered in the UI hierarchy, but not in the actual app? - swift

I'm trying to center a custom UIView in a UITableViewCell. Something fairly basic imo. I used this code to create the UIView, I found it here on stackoverflow:
class SkeletonView: UIView {
var startLocations : [NSNumber] = [-1.0,-0.5, 0.0]
var endLocations : [NSNumber] = [1.0,1.5, 2.0]
var gradientBackgroundColor : CGColor = UIColor(white: 0.85, alpha: 1.0).cgColor
var gradientMovingColor : CGColor = UIColor(white: 0.75, alpha: 1.0).cgColor
var movingAnimationDuration : CFTimeInterval = 0.8
var delayBetweenAnimationLoops : CFTimeInterval = 1.0
lazy var gradientLayer : CAGradientLayer = {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.bounds
gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
gradientLayer.colors = [
gradientBackgroundColor,
gradientMovingColor,
gradientBackgroundColor
]
gradientLayer.locations = self.startLocations
self.layer.addSublayer(gradientLayer)
return gradientLayer
}()
func startAnimating(){
let animation = CABasicAnimation(keyPath: "locations")
animation.fromValue = self.startLocations
animation.toValue = self.endLocations
animation.duration = self.movingAnimationDuration
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
let animationGroup = CAAnimationGroup()
animationGroup.duration = self.movingAnimationDuration + self.delayBetweenAnimationLoops
animationGroup.animations = [animation]
animationGroup.repeatCount = .infinity
self.gradientLayer.add(animationGroup, forKey: animation.keyPath)
}
func stopAnimating() {
self.gradientLayer.removeAllAnimations()
}
}
Then I just added a view to my cell xib, centered horizontally, leading edge +15, top +15 and bottom +15 (with priority 900 so that autolayout doesn't freak out about the height being 320 instead of its calculated value of 320.1). This way it is centered in the xib preview.
When running the app it looks like this, as you can see there is a gap on the left side, but not on the right side, so its not centered:
But then when I take a look at the UI hierarchy, it is centered?!

Remove centered horizontally contraint and add trailing constraint to 15 will resolve your issue
its good enough on my side

Nevermind, I found the problem. The UIView was placed correctly, however the gradientLayer I placed overtop was getting a wrong size. So I added the line
gradientLayer.frame = self.bounds
in the layoutSubviews function inside the skeleton UIView class.

Related

how do i set the background color of UIViews to be a gradient color in swift?

Im trying to set the color of my button and labels to a gradient color in swift with the following code:
extension UIView {
func setGradientColor(colorOne: UIColor, colorTwo: UIColor) {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = [colorOne.cgColor, colorTwo.cgColor]
gradientLayer.locations = [0.0,0.1]
gradientLayer.startPoint = CGPoint(x: 1.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 0.0, y: 0.0)
layer.insertSublayer(gradientLayer, at: 0)
}
}
and then in the sign in form i use the extension like this:
let logo: UILabel = {
let logo = UILabel()
logo.text = "Logo"
logo.setGradientColor(colorOne: UIColor.blue, colorTwo: UIColor.yellow)
logo.textAlignment = .center
logo.font = UIFont.boldSystemFont(ofSize: 59)
return logo
}()
but it still shows up as black.
how do i fix this?
What you posted i review that ... call setGradietColor(colorOne: UIColor.blue, colorTwo: UIColor.yellow) after setting the frame or constraints to the logo view
You can add gradient layer in your view
let gradient: CAGradientLayer = CAGradientLayer()
gradient.colors = [UIColor.black.cgColor, UIColor.blue.cgColor]
gradient.locations = [0.0 , 1.0]
gradient.startPoint = CGPoint(x : 0.0, y : 0)
gradient.endPoint = CGPoint(x :0.0, y: 0.5) // you need to play with 0.15 to adjust gradient vertically
gradient.frame = view.bounds
view.layer.addSublayer(gradient)
You're facing this issue because you're not updating the frame of gradientLayer when bounds of logo updates. For example in ViewController you can give this code if you can have the reference to your gradientLayer.
class ViewController: UIViewController {
var gradient: CAGradientLayer?
let logo: UILabel = {
let logo = UILabel()
logo.text = "Logo"
gradient = logo.setGradietColor(colorOne: UIColor.blue, colorTwo: UIColor.yellow)
logo.textAlignment = .center
logo.font = UIFont.boldSystemFont(ofSize: 59)
return logo
}()
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
gradient?.frame = logo.bounds
}
}
Try returning the layer from your method and modify your function like this:
extension UIView {
#discardableResult
func setGradietColor(colorOne: UIColor, colorTwo: UIColor) {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = [colorOne.cgColor, colorTwo.cgColor]
gradientLayer.locations = [0.0,0.1]
gradientLayer.startPoint = CGPoint(x: 1.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 0.0, y: 0.0)
layer.insertSublayer(gradientLayer, at: 0)
return layer
}
}

Animation in viewDidAppear not working after going background in Swift 3

I'm currently developing a countdown app in Swift 3. Behind the counter, I created a circle, which is animated according to the counter.
My problem is simple: when I click home and I put the app in the background, the animation doesn't work any more when I come back to the app. I don't know why. Interesting fact: the "drawing" is a plain circle, and only the border is animated. The circle's background is still ok, it is only the border animation which is not working any more.
Here is my code :
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let screen = self.view.frame.size.width
let y = CGFloat(190)
let circleWidth = CGFloat(200)
let circleHeight = circleWidth
// Create a new CircleView
let circleView = CircleView(frame: CGRect(x: (screen/2) - (circleWidth/2), y: y, width: circleWidth, height: circleHeight))
view.addSubview(circleView)
// Animate the drawing of the circle over the course of 1 second
circleView.animateCircle(TimeInterval(seconds))
}
CircleView class:
import Foundation
import UIKit
class CircleView: UIView{
var circleLayer: CAShapeLayer!
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
// Use UIBezierPath as an easy way to create the CGPath for the layer.
// The path should be the entire circle.
let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: CGFloat(-M_PI/2), endAngle: CGFloat(M_PI*1.5), clockwise: true)
// Setup the CAShapeLayer with the path, colors, and line width
circleLayer = CAShapeLayer()
circleLayer.path = circlePath.cgPath
circleLayer.fillColor = UIColor(red: 255/255, green: 255/255, blue: 255/255, alpha: 0.2).cgColor
circleLayer.strokeColor = UIColor(red: 165/255, green: 219/255, blue: 255/255, alpha: 0.8).cgColor
circleLayer.lineWidth = 2.0;
// Don't draw the circle initially
circleLayer.strokeEnd = 0.0
// Add the circleLayer to the view's layer's sublayers
layer.addSublayer(circleLayer)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func animateCircle(_ duration: TimeInterval) {
// We want to animate the strokeEnd property of the circleLayer
let animation = CABasicAnimation(keyPath: "strokeEnd")
// Set the animation duration appropriately
animation.duration = duration
// Animate from 0 (no circle) to 1 (full circle)
animation.fromValue = 1
animation.toValue = 0
// Do a linear animation (i.e. the speed of the animation stays the same)
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
// Set the circleLayer's strokeEnd property to 1.0 now so that it's the
// right value when the animation ends.
circleLayer.strokeEnd = 0.0
// Do the actual animation
circleLayer.add(animation, forKey: "animateCircle")
}
}
All you need to do is add one more line in your animation code which is:
animation.isRemovedOnCompletion = false
and your code will be:
func animateCircle(_ duration: TimeInterval) {
// We want to animate the strokeEnd property of the circleLayer
let animation = CABasicAnimation(keyPath: "strokeEnd")
// Set the animation duration appropriately
animation.duration = duration
// Animate from 0 (no circle) to 1 (full circle)
animation.fromValue = 1
animation.toValue = 0
// Do a linear animation (i.e. the speed of the animation stays the same)
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animation.isRemovedOnCompletion = false
// Set the circleLayer's strokeEnd property to 1.0 now so that it's the
// right value when the animation ends.
circleLayer.strokeEnd = 0.0
// Do the actual animation
circleLayer.add(animation, forKey: "animateCircle")
}

Swift - how to animatedly draw line NOT rect? Can only draw rect?

I have looked around about this before but have yet to find an answer. I've been at this for a while and need to know how to animate a LINE as opposed to a rectangle.
From what I can see the animation is very different going from a stroke to a box. Just need some pointers here-
I have a border that is drawn on view load (not with an animation, it just draws it) here:
override public func drawRect(rect: CGRect){
super.drawRect(rect)
let borderColor = self.hasError! ? kDefaultActiveColor : kDefaultErrorColor
let textRect = self.textRectForBounds(rect)
let context = UIGraphicsGetCurrentContext()
let borderlines : [CGPoint] = [CGPointMake(0, CGRectGetHeight(textRect) - 1),
CGPointMake(CGRectGetWidth(textRect), CGRectGetHeight(textRect) - 1)]
if self.enabled {
CGContextBeginPath(context);
CGContextAddLines(context, borderlines, 2);
CGContextSetLineWidth(context, 1.3);
CGContextSetStrokeColorWithColor(context, borderColor.CGColor);
CGContextStrokePath(context);
I got this from another answer and am trying to take it apart. I need the line to animate/grow out from its centre when the view loads, as in grow to the same width and size as it is drawn here but animated.
I do not know how to accomplish this, however another answer achieved the effect I need by setting the border to a size of zero here:
self.activeBorder = UIView(frame: CGRectZero)
self.activeBorder.backgroundColor = kDefaultActiveColor
//self.activeBorder.backgroundColor = kDefaultActiveBorderColor
self.activeBorder.layer.opacity = 0
self.addSubview(self.activeBorder)
and animating using:
self.borderlines.transform = CATransform3DMakeScale(CGFloat(0.01), CGFloat(1.0), 1)
self.activeBorder.layer.opacity = 1
CATransaction.begin()
self.activeBorder.layer.transform = CATransform3DMakeScale(CGFloat(0.03), CGFloat(1.0), 1)
let anim2 = CABasicAnimation(keyPath: "transform")
let fromTransform = CATransform3DMakeScale(CGFloat(0.01), CGFloat(1.0), 1)
let toTransform = CATransform3DMakeScale(CGFloat(1.0), CGFloat(1.0), 1)
anim2.fromValue = NSValue(CATransform3D: fromTransform)
anim2.toValue = NSValue(CATransform3D: toTransform)
anim2.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
anim2.fillMode = kCAFillModeForwards
anim2.removedOnCompletion = false
self.activeBorder.layer.addAnimation(anim2, forKey: "_activeBorder")
CATransaction.commit()
This seems to be using entirely different code than my original drawRect and I don't know how to combine the two. Where am I going wrong here? How can I draw out from the centre my first border as I do the second, active border?
Use CAShapeLater, UIBezierPath and CABasicAnimation to animatedly draw line.
Here is the sample code:
class SampleView: UIView {
override public func drawRect(rect: CGRect) {
let leftPath = UIBezierPath()
leftPath.moveToPoint(CGPointMake(CGRectGetMidX(frame), CGRectGetHeight(frame)))
leftPath.addLineToPoint(CGPointMake(0, CGRectGetHeight(frame)))
let rightPath = UIBezierPath()
rightPath.moveToPoint(CGPointMake(CGRectGetMidX(frame), CGRectGetHeight(frame)))
rightPath.addLineToPoint(CGPointMake(CGRectGetWidth(frame), CGRectGetHeight(frame)))
let leftShapeLayer = CAShapeLayer()
leftShapeLayer.path = leftPath.CGPath
leftShapeLayer.strokeColor = UIColor.redColor().CGColor;
leftShapeLayer.lineWidth = 1.3
leftShapeLayer.strokeEnd = 0
layer.addSublayer(leftShapeLayer)
let rightShpaeLayer = CAShapeLayer()
rightShpaeLayer.path = rightPath.CGPath
rightShpaeLayer.strokeColor = UIColor.redColor().CGColor;
rightShpaeLayer.lineWidth = 1.3
rightShpaeLayer.strokeEnd = 0
layer.addSublayer(rightShpaeLayer)
let drawLineAnimation = CABasicAnimation(keyPath: "strokeEnd")
drawLineAnimation.toValue = NSNumber(float: 1)
drawLineAnimation.duration = 1
drawLineAnimation.fillMode = kCAFillModeForwards
drawLineAnimation.removedOnCompletion = false
leftShapeLayer.addAnimation(drawLineAnimation, forKey: nil)
rightShpaeLayer.addAnimation(drawLineAnimation, forKey: nil)
}
}
The code in viewDidLoad of ViewController
// ...
let sampleView = SampleView()
sampleView.frame = CGRectMake(10, 60, 240, 50)
sampleView.backgroundColor = UIColor.lightGrayColor()
view.addSubview(sampleView)
Here is the capture:
Note: If you run the code in iOS9 Simulator (I have not tested the earlier version), the animation may get blocked. Choose to run it in a real device or in iOS10 Simulator.

Converting a CAShapeLayer to use in an NSImageView

I'm trying to port some iOS code for a Mac app. My code is as follows:
func innerRing() {
let innerRing = CAShapeLayer()
let circleRadius: CGFloat = 105.0
innerRing.frame = InnerRingView.bounds
func circleFrame() -> CGRect {
var circleFrame = CGRect(x: 0, y: 0, width: 2*circleRadius, height: 2*circleRadius)
circleFrame.origin.x = CGRectGetMidX(InnerRingView.bounds) - CGRectGetMidX(circleFrame)
circleFrame.origin.y = CGRectGetMidY(InnerRingView.bounds) - CGRectGetMidY(circleFrame)
return circleFrame
}
innerRing.path = UIBezierPath(ovalInRect: circleFrame()).CGPath
innerRing.lineWidth = 3.0
innerRing.strokeStart = 0.0
innerRing.strokeEnd = 1.0
innerRing.fillColor = UIColor.clearColor().CGColor
innerRing.strokeColor = UIColor(red: 147.0/255.0, green: 184.0/255.0, blue: 255.0/255.0, alpha: 1.0).CGColor
InnerRingView.layer.addSublayer(innerRing)
}
This code works very well, especially for adjusting the fill color, stroke color, and stroke start/end.
In my Mac app, I am effectively trying to use the same code, but apply it to an NSImageView (I want it to be able to appear on each row of a table, and I will adjust certain parameters (such as color) based on what that row details.
Could anyone assist with guidance on adding this simple circle to an NSImageView?
Why do you want to use an NSImageView? NSImageView is for displaying images (icons, pictures, etc).
Make yourself a custom NSView instead. Just remember that, unlike UIKit's UIView, NSView doesn't get a layer by default, so you need to tell It to by setting wantsLayer to true.
Like so:
class CircleView: NSView {
lazy var innerRing: CAShapeLayer = {
let innerRing = CAShapeLayer()
let circleRadius: CGFloat = 105.0
innerRing.frame = self.bounds
var circleFrame = CGRect(x: 0, y: 0, width: circleRadius, height: circleRadius)
circleFrame.origin.x = CGRectGetMidX(self.bounds) - CGRectGetMidX(circleFrame)
circleFrame.origin.y = CGRectGetMidY(self.bounds) - CGRectGetMidY(circleFrame)
innerRing.path = CGPathCreateWithEllipseInRect(circleFrame, nil)
innerRing.lineWidth = 3.0
innerRing.strokeStart = 0.0
innerRing.strokeEnd = 1.0
innerRing.fillColor = NSColor.clearColor().CGColor
innerRing.strokeColor = NSColor(red: 147.0/255.0, green: 184.0/255.0, blue: 255.0/255.0, alpha: 1.0).CGColor
return innerRing
}()
override func awakeFromNib() {
super.awakeFromNib()
wantsLayer = true
layer = CALayer()
layer?.addSublayer(innerRing)
}
}

UIImageView's shadow gets bigger than intended when shadowPath is set

CGRect rect = biggerImageView.bounds;
if([biggerImageView.layer respondsToSelector:#selector(setShadowColor:)])
{
float shadowOffset = rect.size.width * 0.02;
biggerImageView.layer.shadowColor = [UIColor colorWithWhite: 0.25 alpha: 0.55].CGColor;
biggerImageView.layer.shadowOffset = CGSizeMake(shadowOffset, shadowOffset);
biggerImageView.layer.shadowOpacity = 0.8;
// biggerImageView.layer.shadowPath = [UIBezierPath bezierPathWithRect: rect].CGPath;
}
The commented out line causes the shadow becomes bigger than intended.
(vertically longer shadows on top and bottom)
I looked up CALayer reference but got no clue there.
That is probably because the UIView bounds is not the real one yet. (Like in awakeFromNib or viewDidLoad)
Wait for the view layoutSubviews or viewController viewWillLayoutSubviews to be called, and update the path of your shadow there.
I created this swift extension to add the same shadow (with cornerRadius support) everywhere I need to :
import UIKit
extension UIView {
/// Call this from `layoutSubviews` or `viewWillLayoutSubviews`.
/// The shadow will be the same color as this view background.
func layoutShadow() {
// Update frame
if let shadowView = superview?.viewWithTag(978654123) {
shadowView.frame = frame
shadowView.layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius).cgPath
return
}
// Create the shadow the first time
let shadowView = UIView(frame: frame)
shadowView.tag = 978654123
shadowView.translatesAutoresizingMaskIntoConstraints = false
superview?.insertSubview(shadowView, belowSubview: self)
shadowView.layer.shadowColor = (backgroundColor ?? UIColor.black).cgColor
shadowView.layer.shadowOpacity = 0.8
shadowView.layer.shadowOffset = CGSize(width: -2.0, height: 4.0)
shadowView.layer.shadowRadius = 4.0
shadowView.layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius).cgPath
}
}