iOS - CALayer protruding bounds on orientation change - ios

I have a simple CustomView. I have a CAGradientLayer that I am adding to the layer.
#IBDesignable
class CustomView: UIView {
override init (frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
super.layoutSubviews()
setupView()
}
private func setupView() {
let gradient = CAGradientLayer()
gradient.frame = bounds
gradient.colors = [UIColor.blue.cgColor, UIColor.yellow.cgColor]
layer.insertSublayer(gradient, at: 0)
}
}
Below is the Storyboard setup
Below is the result in portrait on iPad, which is fine.
When the device orientation is changed, the layer of the CustomView is protruded from its bounds.
When I checked the view hierarchy in Xcode, the frame of the CustomView is adjusted accordingly, but, it is the layer which is protruding. To cross check, I removed all codes in setupView and set only a background colour, on orientation change, CustomView is adjusted as expected. But, why does it happen for layers?

You are inserting a new layer each time when layoutSubviews method call. That means it shows the line of the first layer.
Just update the frame from layoutSubviews and call setup method from awakeFromNib.
class CustomView: UIView {
private let gradient = CAGradientLayer()
override init (frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
super.layoutSubviews()
gradient.frame = bounds
}
override func awakeFromNib() {
super.awakeFromNib()
setupView()
}
private func setupView() {
gradient.colors = [UIColor.blue.cgColor, UIColor.yellow.cgColor]
layer.insertSublayer(gradient, at: 0)
}
}

Related

How to correctly resize PKDrawing when layout changes?

I'm trying to layer a PKCanvasView on top of an image, which works fine with the initial layout but when the view is resized (by rotating the iPad) the PKDrawing within is not scaled which results in the image annotations being in the incorrect place. I'm struggling to work out how to correctly scale the PKDrawing to preserve the correct positions.
This is an example demonstrating the issue:
class ImageDrawingView: UIView {
private let imageView = UIImageView(frame: .zero)
private let drawingView = PKCanvasView(frame: .zero)
private let toolPicker = PKToolPicker()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
// Setup image view
addSubview(imageView)
imageView.image = UIImage(named: "TestImage") // Here I just used a screen grab of the lock screen
imageView.contentMode = .scaleAspectFill
// Setup drawing view
addSubview(drawingView)
drawingView.isOpaque = false
toolPicker.setVisible(true, forFirstResponder: drawingView)
toolPicker.addObserver(drawingView)
drawingView.becomeFirstResponder()
}
override func layoutSubviews() {
super.layoutSubviews()
imageView.frame = bounds
drawingView.frame = bounds
}
}
class ViewController: UIViewController {
private let imageDrawingView = ImageDrawingView(frame: .zero)
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(imageDrawingView)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
imageDrawingView.frame = view.bounds
}
}
I think if I can calculate the transform, I can then use drawingView.drawing.transform(using: transformScale). How do I go about calculating the transform please?

CAShapeLayer is off center

I have been trying to create a a custom "slider" or knob with UIControl for my app. I found this tutorial and have been using it for some inspiration, but since it does not really accomplish what I want to do I am using it as more of a reference than a tutorial. Anyways, I wrote the code below and found that my CAShapeLayer was not in the center of the UIView that I set to be an instance of my custom class CircularSelector.
Here is my code:
class CircularSelector: UIControl {
private let renderer = CircularSelectorRenderer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
renderer.updateBounds(bounds)
//layer.borderWidth = 4
// layer.borderColor = UIColor.red.cgColor
layer.addSublayer(renderer.selectorLayer)
}
}
class CircularSelectorRenderer {
let selectorLayer = CAShapeLayer()
init() {
selectorLayer.fillColor = UIColor.clear.cgColor
selectorLayer.strokeColor = UIColor.white.cgColor
//Testing
//selectorLayer.borderColor = UIColor.white.cgColor
// selectorLayer.borderWidth = 4
}
private func updateSelectorLayerPath() {
let bounds = selectorLayer.bounds
let arcCenter = CGPoint(x: bounds.midX, y: bounds.maxY)
let radius = 125
var ring = UIBezierPath(arcCenter: arcCenter, radius: CGFloat(radius), startAngle: 0.degreesToRadians, endAngle: 180.degreesToRadians, clockwise: false)
selectorLayer.lineWidth = 10
selectorLayer.path = ring.cgPath
}
func updateBounds(_ bounds: CGRect) {
selectorLayer.bounds = bounds
selectorLayer.position = CGPoint(x: bounds.midX, y: bounds.midY)
updateSelectorLayerPath()
}
}
This is what I get:
and when I uncomment the code under the //Testing line in the init() of the CircularSelectorRenderer I get this:
The grey UIView is of the class CircularSelector. I am confused as to why my CAShapeLayer is wider than the grey view itself and why it is not in the center of the grey view. Why is this happening and how can I fix it.
The problem is that you are updating the shape in the wrong place. Views can (and will) change sizes for different device sizes, superview sizes, device rotation, etc.
You want to update your shape layer when your view is told to layout its subviews:
class CircularSelector: UIControl {
private let renderer = CircularSelectorRenderer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
// no need to call this here
//renderer.updateBounds(bounds)
//layer.borderWidth = 4
// layer.borderColor = UIColor.red.cgColor
layer.addSublayer(renderer.selectorLayer)
}
// add this func
override func layoutSubviews() {
super.layoutSubviews()
// here is where you want to update the layer
renderer.updateBounds(bounds)
}
}

custom UISlider subview frame is offset from main view

my custom uislider background uiview is offset from original view frame.
and the further the the slider original (x,y) from 0,0 the more the offset.
Please check the image below.
uislider subview frame offset
import UIKit
class customSlier: UISlider {
override func awakeFromNib() {
super.awakeFromNib()
self.initializeSomeSettings()
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
private func initializeSomeSettings() {
let view: UIView = UIView()
view.frame = self.frame
view.backgroundColor = UIColor.yellow
self.insertSubview(view, belowSubview: self)
}
override func layoutSubviews() {
super.layoutSubviews()
}
}
I think the problem is at this line:
view.frame = self.frame
Let's say your scroller is at position (10, 10). Then the background frame is also at position (10,10) in the coordinator of the scroll frame, which is (20,20) in the original view. My suggestion is to replace this line by this:
view.frame = CGRect(origin: .zero, size: self.frame.size)
Hope this helps.
try this
view.frame = self.bounds

DrawRect overriding properties of view

I'm creating a custom view which will basically just be a checkmark inside a cirlce. I've created a circle using layer.cornerRadius = 0.5 * frame.width but when I override drawRect, it causes that line to be ignored and my view becomes a square.
Why is this? And how can I fix this so I can create the circle with a checkmark inside it?
Here is my current code:
class CheckMarkView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
layer.cornerRadius = 0.5 * frame.width
backgroundColor = .whiteColor()
hidden = true
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Simply add clipsToBounds to true, and takes all the frame that is available to it.
class CheckMarkView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
layer.cornerRadius = 0.5 * frame.width
backgroundColor = UIColor.whiteColor()
clipsToBounds = true
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func drawRect(rect: CGRect) {
super.drawRect(rect)
}
}

Why I got this strange behavior for UIView with gradient background?

I am trying to add a gradient layer to a UIView (ContentView) that contains a UILabel.
My UIView implementation:
class ContentView: UIView {
override init() {
super.init()
styleIt()
}
override init(frame: CGRect) {
super.init(frame: frame)
styleIt()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
styleIt()
}
private func styleIt() {
var gradient = CAGradientLayer()
gradient.colors = [Utils.Colors.gray_ee.CGColor, Utils.Colors.gray_dd.CGColor]
gradient.frame = bounds
layer.insertSublayer(gradient, atIndex: 0)
layer.borderColor = Utils.Colors.gray_aa.CGColor
layer.borderWidth = 1
layer.cornerRadius = 7.0
}
}
But here is what I got as a result shown in the iPhone 6 iOS Simulator
Only the UIView with cornerRadius is having a strange behavior all other elements in this screenshot are not UIView elements.
I have tried to comment out border-related code and radius code, but still same issue.
Edit: Here is another screenshot with green background for the container (UIScrollView) and all other blah blah labels are within a ContentView element to show the issue in a bigger context.
Try something like this
private var gradient: CAGradientLayer! // Make it a private variable
override init(frame: CGRect) {
// Only init(frame:) and init(coder:) are designated initializers. You don't need to override init(). Internally init() will call init(frame:)
super.init(frame: frame)
commonInit()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
// Create & add the layer
gradient = CAGradientLayer()
layer.addSublayer(gradient)
// You need to set locations, startPoint, endPoint apart from colors
// See Apple documentation for more info
gradient.locations = [0.0, 1.0]
gradient.startPoint = CGPointMake(0, 0)
gradient.endPoint = CGPointMake(0, 1)
gradient.colors = [UIColor.whiteColor().CGColor, UIColor.greenColor().CGColor];
}
override func layoutSubviews() {
super.layoutSubviews()
// bounds may not be correct in init(), better set the layer's frame here
gradient.frame = bounds
}

Resources