How to order views' layers on top of each others in Swift - swift

I added the redView as a subView of the textField and used AutoLayout to constraint it to be as it appears in the image, but the problem here now is
I want to bring the red view to be on top of the border
I tried to use this method
bringSubviewToFront(floatingPlaceholderContainer)
but it didn't work, also tried to use the layer.zPosition = .greatestFiniteMagnitude and it also didn't work
Edit-
the code I used and didn't work as I expected
override func layoutSubviews() {
super.layoutSubviews()
self.redlayer.backgroundColor = UIColor.red.cgColor
self.redlayer.frame = CGRect(x: 10, y: -10, width: 100, height: 30)
self.layer.addSublayer(self.redlayer)
self.redlayer.zPosition = 10
}
and this is the result

You need to configure the timing of when you're adding this layer. This approach works fine for me:
class MyTextField : UITextField {
let redlayer = CALayer()
override func layoutSubviews() {
super.layoutSubviews()
self.redlayer.backgroundColor = UIColor.red.cgColor
self.redlayer.frame = CGRect(x: 10, y: -10, width: 100, height: 30)
self.layer.addSublayer(self.redlayer)
self.redlayer.zPosition = 10
}
}

Related

how to remove border from UIVIEW

I want to know if there is a way to remove the border from view in swift after I have done this:
func addRightBorderWithColor(color: UIColor, width: CGFloat) {
let border = CALayer()
border.backgroundColor = color.cgColor
border.frame = CGRect(x: self.frame.size.width - width,y: 0, width:width,
height:self.frame.size.height)
self.layer.addSublayer(border)
}
I have tried this ->
func removeBorder(){
self.layer.sublayers?.removeAll()
}
but it doesn't work
You should simply save your own reference to this layer, rather than using sublayers. Using sublayers is not prudent as there might be layers other than your border layer.
Thus:
weak var borderLayer: CALayer?
func addRightBorderWithColor(color: UIColor, width: CGFloat) {
let border = CALayer()
border.backgroundColor = color.cgColor
border.frame = CGRect(x: bounds.maxX - width,
y: bounds.minY,
width: width,
height: bounds.height)
layer.addSublayer(border)
self.borderLayer = border
}
func removeBorder() {
borderLayer?.removeFromSuperlayer()
}
This obviously assumes that this was for a subclass of UIView and not an extension. If for the latter, you’d have to use an associated object to keep track of the border layer.
Note, one should not use frame when figuring out where to place the sublayer. Always use bounds.
Taking it a step further, you may want it to gracefully respond to frame changes, too:
override func layoutSubviews {
super.layoutSubviews()
borderLayer?.frame = CGRect(x: bounds.maxX - width,
y: bounds.minY,
width: width,
height: bounds.height)
}

textfield bottom line issue in swift

I am using the following code in Swift 4.2 to have Textfield bottom border:
extension UITextField {
func useUnderline() {
let border = CALayer()
let borderWidth = CGFloat(1.0)
border.borderColor = UIColor.lightGray.cgColor
border.frame = CGRect(origin: CGPoint(x: 0,y :self.frame.size.height - borderWidth), size: CGSize(width: self.frame.size.width, height: self.frame.size.height))
border.borderWidth = borderWidth
self.layer.addSublayer(border)
self.layer.masksToBounds = true
}
}
but in different model of iPhones I get a different behaviors. for example:
in Iphone XR:
and with iPhone X or 8:
Any solution to this issue would be appreciated.
I'm sure this problem does not depend on an iPhone model, but from the place where you call useUnderline(). If you will call useUnderline() from viewDidAppear, then the line will draw correct. Because the size of UIControls not yet actual when calling viewDidLoad and viewWillAppear.
But keep in your mind you will have a problem with your extension because sublayers will be added to UITextField on every viewDidAppear execution.
This could be a prime example where you must use a UITextField subclass instead of an extension. The accepted answer will work, but as suggested there, your TextField will get many layers added to it as the user uses his application.
The below subclass could solve this issue:
class CustomTextField: UITextField{
let border = CAShapeLayer()
override func layoutSubviews() {
super.layoutSubviews()
useUnderline()
}
func useUnderline(){
if layer.sublayers?.contains(border) ?? false{
border.removeFromSuperlayer()
}
border.path = UIBezierPath.init(rect: CGRect.init(x: 0, y: self.bounds.height - 2, width: self.bounds.width, height: 2)).cgPath
border.fillColor = UIColor.init(red: 0, green: 0, blue: 0, alpha: 0.3).cgColor
self.layer.insertSublayer(border, at: 10)
}
}

Force Custom NSView from PaintCode to Redraw

I created an icon in PaintCode that is drawn programmatically (but this question isn't necessarily specific to that tool) and I'm trying to get that icon to redraw.
I use a custom class like this:
class IconTabGeneral: NSView {
override func draw(_ dirtyRect: NSRect) {
StyleKitMac.drawTabGeneral()
}
}
The drawTabGeneral() is a method in the StyleKitMac class (generated by PaintCode) that looks like this (I'll omit all the bezierPath details):
#objc dynamic public class func drawTabGeneral(frame targetFrame: NSRect = NSRect(x: 0, y: 0, width: 22, height: 22), resizing: ResizingBehavior = .aspectFit) {
//// General Declarations
let context = NSGraphicsContext.current!.cgContext
//// Resize to Target Frame
NSGraphicsContext.saveGraphicsState()
let resizedFrame: NSRect = resizing.apply(rect: NSRect(x: 0, y: 0, width: 22, height: 22), target: targetFrame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
context.scaleBy(x: resizedFrame.width / 22, y: resizedFrame.height / 22)
//// Bezier Drawing
let bezierPath = NSBezierPath()
...
bezierPath.close()
StyleKitMac.accentColor.setFill() ⬅️Custom color set here
bezierPath.fill()
NSGraphicsContext.restoreGraphicsState()
}
The accentColor defined there is a setting that can be changed by the user. I can't get my instance of IconTabGeneral to redraw to pick up the new color after the user changes it.
I've tried this without any luck:
iconGeneralTabInstance.needsDisplay = true
My understanding is that needsDisplay would force the draw function to fire again, but apparently not.
Any idea how I can get this icon to redraw and re-fill its bezierPath?
How about using a NSImageView? Here is a working example:
import AppKit
enum StyleKit {
static func drawIcon(frame: CGRect) {
let circle = NSBezierPath(ovalIn: frame.insetBy(dx: 1, dy: 1))
NSColor.controlAccentColor.setFill()
circle.fill()
}
}
let iconView = NSImageView()
iconView.image = .init(size: .init(width: 24, height: 24), flipped: true) { drawingRect in
StyleKit.drawIcon(frame: drawingRect)
return true
}
import PlaygroundSupport
PlaygroundPage.current.liveView = iconView
I think I got it. It turns out the PaintCode-generated StyleKitMac class was caching the color. The icon was, in fact, being redrawn after needsDisplay was being set. So I just had to refresh the cache's color value.

How can I animate a gradient layer which increases its width?

I'm using a simple UIView which I want to animate and I added a gradient layer to it.
I want to increase the width of the view and the layer placed on the view,
but all I get is that the view increases its width but not the layer.
Example: Let's say I have a UIView with height = width = 50
I animate it by setting the width to: width += 50. This animation is working. If I do the same with layer then noting happens. The layer does not increase its width. I tried some things to fix this (see comments in code) but nothing is working.
Here is my code
func performNextTitleAnimation() {
let overlayViewHeight = overlayView.frame.size.height
let overlayViewWidth = overlayView.frame.size.width
let animationHeight: CGFloat = 48
let overlayViewHalfHeight = (overlayViewHeight) / 2
swipeAnimation = UIView(frame: CGRect(x: 0, y: overlayViewHalfHeight - (animationHeight/2), width: 48, height: animationHeight))
swipeAnimation.backgroundColor = .gray
swipeAnimation.layer.cornerRadius = swipeAnimation.frame.size.height / 2
gradientLayer.frame = swipeAnimation.bounds
overlayView.addSubview(swipeAnimation)
swipeAnimation.layer.addSublayer(gradientLayer)
UIView.animate(withDuration: 1.2, delay: 0, options: [.repeat], animations: {
self.gradientLayer.cornerRadius = 24
self.swipeAnimation.frame.size.width += 50
//Things I tried, but not working
//1.) self.gradientLayer.frame.size.width += 50
//2.) self.gradientLayer.frame.size.width = self.swipeAnimation.frame.size.width
//3.) self.gradientLayer.bounds = self.swipeAnimation.bounds
}, completion: nil)
}
Gradient Layer
gradientLayer.colors = [UIColor.white.cgColor, animationColor.cgColor]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.transform = CATransform3DMakeRotation(3*(CGFloat.pi) / 2, 0, 0, 1)
Any help is highly appreciated.
The best result I've ever achieved when I needed to animate CAGrandientLayers size is to use it as a layerClass of a custom UIView:
class GrandientView: UIView {
override class var layerClass : AnyClass {
return CAGradientLayer.self
}
var gradientLayer: CAGradientLayer {
// it is safe to force cast here
// since we told UIView to use this exact type
return self.layer as! CAGradientLayer
}
override init(frame: CGRect) {
super.init(frame: frame)
// setup your gradient
}
}

Add subView behind items to UINavigationBar in iOS 11

In my UINavigationBar subclass I am adding a subView with insertSubview(customView, at: 0) in order to place it in the very 'back' of the UINavigationBar.
In iOS 10 the added subView will be added the Title and UIBarButtonItems as I want it, but in in iOS 11 it seems like Apple changed something because no matter what I try the customView will be placed every time in the front of the UINavigationBar.
//containerView
containerView.frame = CGRect(x: 0, y: -(statusBarHeight), width: UIScreen.main.bounds.width, height: frame.height + statusBarHeight)
containerView.clipsToBounds = true
insertSubview(containerView, at: 0)
I already tried changing the index.
Any idea how I should tackle that problem?
You can look at this. I solved it this way:
override func layoutSubviews() {
super.layoutSubviews()
for aView in self.subviews {
if NSStringFromClass(type(of: aView)) == "_UINavigationBarContentView" {
aView.frame = CGRect(x: 0, y: 20, width: aView.frame.width, height: aView.frame.height)
}
else if NSStringFromClass(type(of: aView)) == "_UIBarBackground" {
aView.frame = CGRect(x: 0, y: 0, width: aView.frame.width, height: self.frame.height)
}
}
}