How to Insert a Sublayer in Swift

How to insert a sublayer in swift?

You can use the following snippet

view.layer.insertSublayer(yourLayer, atIndex: yourIndex)

Adding a sublayer to a UIView (background view) of UITableViewCell covers other cell views?

Just use

backgroundRect.layer.insertSublayer(gradient, at: 0) 

Instead of using

backgroundRect.layer.addSublayer(gradient)

Problem when inserting a sublayer below an existing layer in Swift?

You can try

self.mainView.insertSubview(focusBG, belowSubiew: tapView)

Or

self.mainView.layer.insertSublayer(focusBG.layer, below: tapView.layer) // not tested 

Adding a sub layer or UIView as subview to the top using z-order of UIButton still goes behind the view or look transparent

As I understand you set borderWidth and borderColor for button.layer.
This button.layer hosts all other sublayer that you add to them that's why your border is always on the top.

So you can use additional view for drawing the circle or...
Just remove setting borderWidth and borderColor for button.layer and add another sublayer that will cover your border.

For example:

    func addBorder(borderWidth: CGFloat = 1.0, borderColor: UIColor = UIColor.blue) {
let layer = CALayer()
let frame = self.mybtn.frame
layer.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height)
layer.borderColor = borderColor.cgColor
layer.borderWidth = borderWidth
self.mybtn.layer.addSublayer(layer)
}

and in ViewDidLoad:

    override func viewDidLoad() {
super.viewDidLoad()
addBorder()
addCircleBadge(20, color: UIColor.red)
}

Also one update for addCircleBadge

    public func addCircleBadge(_ width: CGFloat, color: UIColor) {
...

self.mybtn.layer.insertSublayer(layer, at: 0)
}

Why adding sublayer overlaps subviews?

When you add your UILabel as a subview of your view it is adding your UILabel's layer as a sublayer of your view's layer. So when you add another sublayer to your view's layer it will be on top of your UILabel's layer.

You can either add your background layer before you add the UILabel or do:

Swift

view.layer.insertSublayer(backgroundLayer, below: yourLabel.layer)

Objective C

[view.layer insertSublayer:backgroundLayer below:yourLabel.layer]

and it should put the background behind the label.

add textview as a sublayer of an image ios swift

Just use UITextView or UITextField and add it as subview to the UIImageView, something like:

let textView = UITextView()
//configure text view like you want
//add constraints or size it as you want
myImage.addSubview(textView)

Remember UIImageView is just another subclass of UIView so you can add subviews to it like for a regular UIView.


Going on to what you are dealing with, since some of the views you add are CALayers and some will be UIViews or subclasses of both (for instance UITextView is UIView subclass)

I would add two properties to your class:

var addedViews = [Any]()
var undoStack = [Any]()

I know this is not very Swift like since you can put anything into these two arrays but still.

Then when you create a new layer or view you also add it to addedViews array:

let layer = CAShapeLayer()
layer.frame = CGRect(x: 30, y: 30, width: 100, height: 100)
layer.backgroundColor = UIColor.red.cgColor
layer.transform = CATransform3DMakeRotation(0.2, 0, 0, 1)
addedViews.append(layer)

So addedViews array will hold references to all the views you added, so when you undo you can do just the following:

if let viewLayer = addedViews.last {
if let view = viewLayer as? UIView {
view.removeFromSuperview()
} else if let layer = viewLayer as? CALayer {
layer.removeFromSuperlayer()
}

undoStack.append(viewLayer)
addedViews.removeLast()
}

If you then want to redo the change you do the same thing but you get the last view from undoStack like so:

if let viewLayer = undoStack.last {
if let view = viewLayer as? UIView {
self.view.addSubview(view)
} else if let layer = viewLayer as? CALayer {
view.layer.addSublayer(layer)
}

addedViews.append(viewLayer)
undoStack.removeLast()
}

Swift iOS - How to line up a sublayer position to match the center of button text

Here's a button that seems to look the way you want (of course you can adjust any parameters that don't suit your sensibilities):

Sample Image

This button is automatically red, corner-rounded, and considerably larger than its text (even when the button is positioned using auto layout).

Here's how it was achieved through a subclass:

class MyRedButton : UIButton {
override func layoutSubviews() {
super.layoutSubviews()
self.backgroundColor = .red
self.layer.cornerRadius = 10
}
override var intrinsicContentSize: CGSize {
var sz = super.intrinsicContentSize
sz.width += 30; sz.height += 30
return sz
}
}

Add a CALayer sublayer centered over UIImageView

The issue is that when viewDidLoad is called the layout has not been done yet so your measurements are being taken from the initial state of the view controller , most likely from the storyboard size if thats what you are using.

You need to call blurFilterMask once layout has completed and the final size of heroImageView is known.

You can do this by overriding viewDidLayoutSubviews which is ...

Called to notify the view controller that its view has just laid out its subviews.

Read the docs as there are some conditions but in your case this does work leaving you with this..

class ViewController: UIViewController {

@IBOutlet var heroImageView: UIImageView!

var blurFilterMask:BlurFilterMask! = nil

func resetMaskOverlay(){

if blurFilterMask == nil {

blurFilterMask = BlurFilterMask()

blurFilterMask.diameter = 120

heroImageView.layer.addSublayer(blurFilterMask)

}

blurFilterMask.frame = heroImageView.bounds
blurFilterMask.origin = heroImageView.center

}

override func viewDidLayoutSubviews() {
resetMaskOverlay()
}

}

I assumed this line in BlurFilterMask

    CGContextTranslateCTM(ctx, 0.0, yDiff)*/

Was meant to be commented out as it didn't make much sense leaving...

class BlurFilterMask : CALayer {

let GRADIENT_WIDTH : CGFloat = 50.0

var origin : CGPoint = CGPointZero {
didSet {
setNeedsDisplay()
}
}
var diameter : CGFloat = 50.0 {
didSet {
setNeedsDisplay()
}
}

override func drawInContext(ctx: CGContext) {
CGContextSaveGState(ctx)

let clearRegionRadius : CGFloat = self.diameter * 0.5
let blurRegionRadius : CGFloat = clearRegionRadius + GRADIENT_WIDTH

let baseColorSpace = CGColorSpaceCreateDeviceRGB();
let colours : [CGFloat] = [0.0, 0.0, 0.0, 0.0, // Clear region
0.0, 0.0, 0.0, 0.6] // blur region color
let colourLocations : [CGFloat] = [0.0, 0.0]
let gradient = CGGradientCreateWithColorComponents (baseColorSpace, colours, colourLocations, 2)

CGContextDrawRadialGradient(ctx, gradient, self.origin, clearRegionRadius, self.origin, blurRegionRadius, .DrawsAfterEndLocation);

}

}

test1-5s test1-6s+

Can't access sublayer after adding a UIView into current UIView

Quick explanation:

When you do self.layer.sublayers as? [CAShapeLayer], you are supposing that ALL self.layer.sublayers are of type CAShapeLayer. But, are you sure about that, who told you this? There are other kind of CALayer, and default components might come with some already. That's why it's failing. The idea, is with to use compactMap() to keep only the CAShapeLayer:

let sublayers = self.layer.sublayers.compactMap { $0 as? [CAShapeLayer] }

Detailed explanation:

if let sublayers = self.layer.sublayers as? [CAShapeLayer] {} else {}

The else is called because self.layer.sublayers is not cartable into, it's not a [CAShapeLayer]. In our case, they are layers, so it means that not all sublayer is a CAShapeLayer.
So, let's keep only the CAShapeLayer, and forget about the other one (CAGradientLayer, CALayer, etc.).
For that, we can use compactMap().

self.layer.sublayers.compactMap { aSublayer in

}

The closure logic is simple:
The array is iterated. On each iteration, the item is called aSublayer. We do whatever we want, and we return a transformed value if needed, an Int, etc.
If we don't want to keep the transformed value, we return nil, and then it will be skipped.

Since we want to keep only the sublayers which are a CAShapeLayer, we can use a simple cast on them:

if let aSubLayerAsShapeLayer = aSubLayer as? CAShapeLayer {
return aSubLayerAsShapeLayer
} else { //We won't keep it
return nil
}

Which is equivalent to

return aSubLayer as? CAShapeLayer

Then, with more simplification (Implicit Returns from Single-Expression Closures, Shorthand Argument Names, more info on the doc), we get:

let sublayers = self.layer.sublayers.compactMap { $0 as? [CAShapeLayer] }

Also, since it's "sure" to have an array (might be empty), it's not an optional anymore, we remove the if let into a let.



Related Topics



Leave a reply



Submit