Gradient sublayer opacity - ios

I wrote my function to insert a layer (addsubview) to my main view, i gave it vars to manage size and colors for its gradient background with an opacity at 1. I inserted with an index etc...
Now, i would like to manage the opacity of this gradient (background) sublayer through actions :
I'm able to remove it, play with the general opacity of the subview... but impossible to target the opacity of this sublayer at index 0.
Any idea ?
in my viewdidLoad func :
func insertHeader () {
self.view.addSubview(TopMenuView)
TopMenuView.frame.size.width = self.view.bounds.size.width
let gradient:CAGradientLayer = CAGradientLayer()
let colorTop = UIColor(RGBa).cgColor
let colorBottom = UIColor(RGBa).cgColor
//etc
gradient.opacity = 1.0
TopMenuView.layer.insertSublayer(gradient, at: 0)
}
And later, unable to target the opacity of this sublayer gradient...
I can manage the whole opacity of the TopMenuView.layer but not its "background gradient layer"

You should be able to create a reference to the gradient layer...
class ViewController: UIViewController {
var topMenuGradient = CAGradientLayer()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(TopMenuView)
TopMenuView.frame.size.width = self.view.bounds.size.width
let colorTop = UIColor(RGBa).cgColor
let colorBottom = UIColor(RGBa).cgColor
//etc
topMenuGradient.frame = TopMenuView.bounds
topMenuGradient.opacity = 0.5
TopMenuView.layer.insertSublayer(topMenuGradient, at: 0)
}
#IBAction func btnTapped(_ sender: Any) {
topMenuGradient.opacity = 0.1
}
}

Related

Using UIColor(patternImage:) for UIView's tintColor?

I am using a UISegmentControl and am trying to give each segment's view a custom UIColor. This has been easy to do for solid colors. I simply do:
providerSegmentedControl.subviews[1].tintColor = UIColor.red
And the segment at index 1 will now be tinted with the color red.
However, I am trying to add a gradient for the tintColor of one of my segment's UIView. I have an image in my assets called "AppleMusicGradient" that looks like this:
I am trying to create a UIColor from this image using UIColor(patternImage:). The code looks like this:
//UIImage.resize is a UIImage extension function that simply resizes a UIImage to the given CGRect
let image = UIImage(named: "AppleMusicGradient")!.resize(targetSize: providerSegmentedControl.subviews[2].frame.size)
providerSegmentedControl.subviews[2].tintColor = UIColor(patternImage: image)
However, this method seems to only be partially working. When the given segment is selected, this gradient isn't visible at all. And when the given segment is unselected, the gradient is only visible on the label inside of the segment, i.e. the border normally seem is not present.
See the "Apple Music" segment.
My solution feels a little "hacky" but I believe this may be what you are looking for.
#IBOutlet weak var segmentedControl: UISegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.updateGradientBackground()
}
private func updateGradientBackground() {
let sortedViews = segmentedControl.subviews.sorted( by: { $0.frame.origin.x < $1.frame.origin.x } )
for (index, view) in sortedViews.enumerated() {
if index == segmentedControl.selectedSegmentIndex {
view.backgroundColor = UIColor(patternImage: UIImage(named: "7Wk4B")!)
view.tintColor = UIColor.clear
let titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
UISegmentedControl.appearance().setTitleTextAttributes(titleTextAttributes, for: .selected)
} else {
view.backgroundColor = UIColor.white
view.tintColor = UIColor.blue
}
}
}
#IBAction func segmentedControllerTapped(_ sender: Any) {
self.updateGradientBackground()
}

Change color to a shape added as subview

I created a UIView, called CircleView, that draws a circle. This was added with a click of a button and located with a UITapGestureRecognizer in another view, canvasView, as a subview.
This is the code for circle and it works:
#IBAction func tapCircle(_ sender: UITapGestureRecognizer) {
let tapPoint2 = sender.location(in: canvasView)
let shapeCircle = CircleView(origin: tapPoint2)
canvasView.addSubview(shapeCircle)
shapeCircle.tag = 300
}
#IBAction func circleDraw(_ sender: UIButton) {
canvasView.isUserInteractionEnabled = true
tapCircle.isEnabled = true
tapRect.isEnabled = false
tapGRQ.isEnabled = false
canvasView.setNeedsDisplay()
}
It's all okay but i'm wondering if it is possible to change the color of this shape with a click of a button. Thanks a lot.
Create global variables for arrays of shapes
var circleShapes = [UIView]()
var squareShapes = [UIView]()
...
then append new UIView to certain array after you create it
let shapeCircle = CircleView(origin: tapPoint2)
canvasView.addSubview(shapeCircle)
circleShapes.append(shapeCircle)
...
now just change backgroundColor for each view inside certain array
circleShapes.forEach { $0.backgroundColor = /* UIColor */ }
...
Or if your canvasView contains just shapes subviews, every shape has its own UIView subclass and you want to change colors in the same time, you can change backgroundColor depending on type of shape
canvasView.subviews.forEach {
switch $0 {
case is CircleView:
$0.backgroundColor = /* UIColor */
case is SquareView:
$0.backgroundColor = /* UIColor */
...
default:
continue
}
}

I'm having trouble getting this CA animation to work

I'm trying to make an animation where the color red pulses on the screen and when the screen is tapped the speed of the pulse increases. Right now I have two issues. One, is that the animation does not occur, the second is that the animation must speed up without restarting the animation. I want the animation to speed up from whatever point it is at when the screen is tapped.
class ViewController: UIViewController {
var tapCount: Int = 0
var pulseSpeed: Double = 3
let pulseAnimLayer = CALayer()
let pulseAnim = CABasicAnimation(keyPath: "Opacity")
override func viewDidLoad() {
super.viewDidLoad()
counter.center = CGPoint(x: 185, y: 118)
pulseAnim.fromValue = 0.5
pulseAnim.toValue = 1.0
pulseAnim.duration = 3.0
pulseAnim.autoreverses = true
pulseAnim.repeatCount = .greatestFiniteMagnitude
pulseAnimLayer.add(pulseAnim, forKey: "Opacity")
}
func pulseAnimation(pulseSpeed: Double) {
UIView.animate(withDuration: pulseSpeed, delay: 0,
options: [UIViewAnimationOptions.repeat, UIViewAnimationOptions.autoreverse],
animations: {
self.red.alpha = 0.5
self.red.alpha = 1.0
}
)
}
#IBOutlet weak var red: UIImageView!
#IBOutlet weak var counter: UILabel!
#IBAction func screenTapButton(_ sender: UIButton) {
tapCount += 1
counter.text = "\(tapCount)"
pulseSpeed = Double(3) / Double(tapCount)
pulseAnim.duration = pulseSpeed
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
I didn't read this carefully enough the first time.
You've got an odd mix here of core animation and UIView animation. The two exist at different levels. Core Animation will allow you to animate a property on a view (say it's background color).
UIView animation is built on top of Core Animation and makes it easier to animate view changes automatically by building the underlying Core Animations.
You start with Core Animation when you create a CABasicAnimation but then you have a function, pulseAnimation which looks to be doing something using UIVIew animations. I think you can get rid of that function entirely.
When you add your CABasicAnimation you tell it to change the value of the layer's "Opacity". What you probably want there is "opacity" (lower case).
Your animation is being applied to a new CALayer that you've created... but you don't seem to ever put that layer into the view (so it is never drawn). More likely than not what you should probably do is attach your animation to the view's layer and just allow that layer to have it's color animated.
To add one layer to another use addSublayer (https://developer.apple.com/reference/quartzcore/calayer/1410833-addsublayer). The view's layer from your code should be available as self.view.layer though you may have to use it in viewWillAppear instead of in viewDidLoad
You need to set the alpha of the red imageview to 0.0 before animating
func pulseAnimation(pulseSpeed: Double) {
UIView.animate(withDuration: pulseSpeed, delay: 1.0,
options: [UIViewAnimationOptions.repeat, UIViewAnimationOptions.autoreverse],
animations: {
self.red.alpha = 0.5
self.red.alpha = 1.0
}
)
}
override func viewDidAppear(_ animated: Bool) {
self.red.alpha = 0.0
counter.center = CGPoint(x: 185, y: 118)
}

CALayer appears only for one second, then disappears

I'm adding CALayer to top and bottom of scrollable objects (UIScrollView, TableView, CollectionView) to display them when there is a content behind the visible area.
class TableViewWithCALayers: UITableView {
var topGradientLayer: CAGradientLayer?
var bottomGradientLayer: CAGradientLayer?
override func layoutSubviews() {
super.layoutSubviews()
guard self.topGradientLayer != nil && self.bottomGradientLayer != nil else {
addGradientLayerToTop() // create layer, set frame, etc.
addGradientLayerToBottom()
return
}
// addGradientLayerToTop()// if uncomment it - multiple layers are created and they are visible, but this is not the solution...
handleLayerAppearanceAfterLayoutSubviews() // playing with opacity here
}
How I create layer:
func addGradientLayerToTop() {
if let superview = superview {
self.topGradientLayer = CAGradientLayer()
let colorTop = UIColor.redColor().CGColor
let colorBottom = UIColor.clearColor().CGColor
if let topLayer = self.topGradientLayer {
topLayer.colors = [colorTop, colorBottom]
topLayer.locations = [0.0, 1.0]
topLayer.frame = CGRect(origin: self.frame.origin, size: CGSizeMake(self.frame.width, self.layerHeight))
superview.layer.insertSublayer(topLayer, above: self.layer)
if (self.contentOffset.y == 0) {
// if we are at the top - hide layer
// topLayer.opacity = 0.0 //temporarily disabled, so it is 1.0
}
}
}
}
TableViewWithCALayers works nice everywhere, except using TableView with xib files:
class XibFilesViewController : CustomUIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: TableViewWithCALayers!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.registerNib(UINib(nibName: "CustomTableViewCell", bundle: NSBundle.mainBundle()), forCellReuseIdentifier: "CustomTableViewCell")
self.tableView.layer.masksToBounds = false // this line doesn't help...
}
CustomUIViewController is used in many other ViewControllers where TableViewWithCALayers works good, so it should not create a problem.
Layers at the top and bottom appear for one second, then disappear. Logs from LayoutSubviews() func say that they are visible and opacity are 1.0, but something covers them. What can it be and how to deal with that?
Any help is appreciated!)
topLayer.zPosition = 10000 //doesn't help
topLayer.masksToBounds = false //doesn't help as well
When using nib files it's good practice, and design to add the UIView that you want to draw the layer on into your prototype cell, or header/footed and then have that view confirm to your class that's actually handling the layer.

Undo Button Swift

I have a drawing app that uses CALayer sublayers for the actual drawing of an image. I currently have an IBAction that contains code to remove the sublayers from the superlayer. However everytime I run it, I get a BAD ACCESS error that crashes my app. I'm wondering why it won't allow me to remove the sublayers. Also, in theory, this approach would remove all of the layers entirely. I would ideally want it to only undo the last layers that were drawn. Any suggestions on what I should do? Thanks.
var locale: CALayer {
return layerView.layer
}
#IBAction func undoButton(sender: AnyObject) {
var sublayers = self.view.layer.sublayers
for layer in sublayers {
layer.removeFromSuperlayer()
}
}
func setUpLayer() -> CALayer {
locale.contents = image
locale.contentsGravity = kCAGravityCenter
return locale
}
func subLayerDisplay() {
var newLayer = CALayer()
var tempRect = CGRectMake(prevLocation.x, prevLocation.y, 50, 50)
newLayer.frame = tempRect
newLayer.contents = image
self.view.layer.insertSublayer(newLayer, below: locale)
}
To sum up, you can add your layer to a view, and then deal with the view, which will make life much more easier.
When you add the button, you can do
view.tag = 1
self.addSubView(view)
When you want to remove it
let view = self.viewWithTag(1) as! UIView
view.removeFromSuperView()

Resources