Gradient background iPhone dimensions on iPad - ios

I have a problem setting gradient background on iPad. On iPhones everything is ok but when I use iPad the gradient background has iPhone dimensions.
The code that I use to make the gradient is the on below.
func setGradientToTableView(tableView: UITableView) {
let gradientBackgroundColors = [UIColor(red: 190.0/255.0, green: 219.0/255.0, blue: 0.0/255.0, alpha: 1).cgColor, UIColor(red: 13.0/255.0, green: 227.0/255.0, blue: 97.0/255.0, alpha: 1).cgColor]
let gradientLayer = CAGradientLayer()
gradientLayer.colors = gradientBackgroundColors
gradientLayer.startPoint = CGPoint(x: 0,y: 0)
gradientLayer.frame = tableView.bounds
let backgroundView = UIView(frame: tableView.bounds)
backgroundView.layer.insertSublayer(gradientLayer, at: 0)
tableView.backgroundView = backgroundView
}

You need to set frame for layer in viewDidLayoutSubviews:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.backgroundView?.layer.sublayers?.forEach { $0.frame = tableView.bounds }
}
Also, as #Jan Schlorf suggests in comment, you can store your layer as property:
lazy var gradientLayer = CAGradientLayer()
//...
func setGradientToTableView(tableView: UITableView) {
let gradientBackgroundColors = [UIColor(red: 190.0/255.0, green: 219.0/255.0, blue: 0.0/255.0, alpha: 1).cgColor, UIColor(red: 13.0/255.0, green: 227.0/255.0, blue: 97.0/255.0, alpha: 1).cgColor]
gradientLayer.colors = gradientBackgroundColors
gradientLayer.startPoint = CGPoint(x: 0,y: 0)
gradientLayer.frame = tableView.bounds
let backgroundView = UIView(frame: tableView.bounds)
backgroundView.layer.insertSublayer(gradientLayer, at: 0)
tableView.backgroundView = backgroundView
}
//...
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
gradientLayer.frame = tableView.bounds
}

I assume you didn't use autolayout?
Check your tableviewsize when starting your app with iPad Simulator. I think it is not as big as you think it is.

Related

incorrect Navigation bar background image coloring for iphone 12 mini

I'm setting the image of the navigation bar to a gradient. This works perfectly on all models except for iPhone 12 mini.
I've tried calling this on my main view controller in ViewWillAppear, viewDidAppear, and ViewDidLoad
Here's what it looks like on all other models
func setNavGradiant(){
guard let navigationController = self.navigationController else {print("❇️♊️>>>\(#file) \(#line): guard let failed<<<"); return}
let gradientLayer = CAGradientLayer()
var updatedFrame = navigationController.navigationBar.bounds
updatedFrame.size.height += UIApplication.shared.windows[0].windowScene?.statusBarManager?.statusBarFrame.height ?? 0
gradientLayer.frame = updatedFrame
gradientLayer.colors = [ #colorLiteral(red: 0.4392156899, green: 0.01176470611, blue: 0.1921568662, alpha: 1).cgColor, #colorLiteral(red: 0.2196078449, green: 0.007843137719, blue: 0.8549019694, alpha: 1).cgColor] // start color and end color
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.0) // vertical gradient start
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
UIGraphicsBeginImageContext(gradientLayer.bounds.size)
gradientLayer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.navigationController?.navigationBar.setBackgroundImage(image, for: UIBarMetrics.default)
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor(#colorLiteral(red: 0.9579688907, green: 0.9579688907, blue: 0.9579688907, alpha: 1))]
}
You will probably have better results by subclassing UINavigationController:
class MyNavigationController: UINavigationController {
let gradient = CAGradientLayer()
override func viewDidLoad() {
super.viewDidLoad()
gradient.frame = navigationBar.bounds
gradient.colors = [ #colorLiteral(red: 0.4392156899, green: 0.01176470611, blue: 0.1921568662, alpha: 1).cgColor, #colorLiteral(red: 0.2196078449, green: 0.007843137719, blue: 0.8549019694, alpha: 1).cgColor] // start color and end color
gradient.startPoint = CGPoint(x: 0.5, y: 0.0) // vertical gradient start
gradient.endPoint = CGPoint(x: 0.5, y: 1.0)
if let image = getImageFrom(gradientLayer: gradient) {
navigationBar.setBackgroundImage(image, for: UIBarMetrics.default)
}
}
func getImageFrom(gradientLayer:CAGradientLayer) -> UIImage? {
var gradientImage:UIImage?
UIGraphicsBeginImageContext(gradientLayer.frame.size)
if let context = UIGraphicsGetCurrentContext() {
gradientLayer.render(in: context)
gradientImage = UIGraphicsGetImageFromCurrentImageContext()?.resizableImage(withCapInsets: UIEdgeInsets.zero, resizingMode: .stretch)
}
UIGraphicsEndImageContext()
return gradientImage
}
}
Try calling:
DispatchQueue.main.async {
self.setNeedsStatusBarAppearanceUpdate()
}
after your setup is done. Should work without dispatching to main thread, but I had previously caught issues of things called from lifecycle methods not being on the main thread. So another thing to check is to make sure you are running the updates on the main thread.

Why gradient layer in UITableViewCell not cover entire frame?

I try to make a standard gradient top-bottom with long UIView. But it's not full. The nib is part of UITableViewCell, so I don't have access to viewDidLayoutSubviews() as in this thread.
I've tried to call contentView.layoutIfNeeded() from the code version of this view. I called it when UITableView cellForRowAtIndexPath gets called. But it's no effect.
I prepare the gradient in awakeFromNib().
let colors = [
UIColor(red: 33/255.0, green: 33/255.0, blue: 33/255.0, alpha: 1.0).cgColor,
UIColor(red: 51/255.0, green: 51/255.0, blue: 51/255.0, alpha: 1.0).cgColor]
let gradient = CAGradientLayer()
gradient.frame = gradientView.bounds
gradient.colors = colors
gradientView.layer.insertSublayer(gradient, at: 0)
Is there something wrong with my code?
You should use view's boundsinstead of frame, because your layer is inside of the view, and the frame may have an offset.
The layout of the view is changed after awakeFromNib. So you should resize the layer in layoutSubviews of the view. For this create a property gradient and:
let gradient: CAGradientLayer = CAGradientLayer()
override func awakeFromNib() {
...
let colors = [
UIColor(red: 33/255.0, green: 33/255.0, blue: 33/255.0, alpha: 1.0).cgColor,
UIColor(red: 51/255.0, green: 51/255.0, blue: 51/255.0, alpha: 1.0).cgColor]
gradient.frame = bounds
gradient.colors = colors
gradientView.layer.insertSublayer(gradient, at: 0)
...
}
override func layoutSubviews() {
super.layoutSubviews()
gradient.frame = bounds
}
EDIT: An alternative is a custom view with own layer class:
public class GradientLayer: UIView {
#IBInspectable var startColor: UIColor! = UIColor(red: 33/255.0, green: 33/255.0, blue: 33/255.0, alpha: 1.0)
#IBInspectable var endColor: UIColor! = UIColor(red: 51/255.0, green: 51/255.0, blue: 51/255.0, alpha: 1.0)
override class var layerClass : AnyClass {
return CAGradientLayer.self
}
override func awakeFromNib() {
super.awakeFromNib()
let colors = [ startColor.cgColor, endColor.cgColor ]
if let gradient = self.layer as? CAGradientLayer {
gradient.colors = colors
}
}
}
This is more elegant, because you can replace the static colors with IB inspectables, and you have a reusable component view.
override func layoutSubviews() {
super.layoutSubviews()
if let gradient = baseView.layer.sublayers?[0] as? CAGradientLayer {
gradient.frame = self.bounds
}
}

Gradient Layer in Navigation Bar in iOS 11

After adding gradient layer in navigation bar, I don't see any right/left bar button items when i run on iOS 11. But the same code displays well on iOS 10/9..
Can anyone provide your valuable suggestions to fix this
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
setUpGradientNavigationBar()
}
func setUpGradientNavigationBar() {
let lightRedColor = UIColor(red: CGFloat(197/255.0), green: 47/255.0, blue: 40/255.0, alpha: 1.0).cgColor
let mediumRedColor = UIColor(red: CGFloat(176/255.0), green: 42/255.0, blue: 36/255.0, alpha: 1.0).cgColor
let darkRedColor = UIColor(red: CGFloat(106/255.0), green: 25/255.0, blue: 22/255.0, alpha: 1.0).cgColor
let colors = NSArray(objects: lightRedColor, mediumRedColor, darkRedColor)
let gradientLayer = getGradientLayerForColors(colors, location: 0.5, andFrame: self.navigationController?.navigationBar.bounds)
self.navigationController?.navigationBar.barTintColor = UIColor.black
self.navigationController?.navigationBar.layer.insertSublayer(gradientLayer, at: 1)
self.navigationController?.navigationBar.tintColor = UIColor.white
self.navigationController?.navigationBar.topItem?.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.cancel, target: nil, action: nil)
}
func getGradientLayerForColors(_ colors: NSArray, location:CGFloat, andFrame frame:CGRect?) -> CAGradientLayer {
let gradientLayer = CAGradientLayer()
gradientLayer.colors = colors as [AnyObject]
gradientLayer.locations = [0.0,NSNumber.init(value: Float(location))]
gradientLayer.frame = frame!
return gradientLayer
}
Swift 5
Includes status bar in gradient.
Doesn't use an image.
Uses transparency so content is visible through nav bar.
extension UINavigationBar {
func addGradient(_ toAlpha: CGFloat, _ color: UIColor) {
let gradient = CAGradientLayer()
gradient.colors = [
color.withAlphaComponent(toAlpha).cgColor,
color.withAlphaComponent(toAlpha).cgColor,
color.withAlphaComponent(0).cgColor
]
gradient.locations = [0, 0.8, 1]
var frame = bounds
frame.size.height += UIApplication.shared.statusBarFrame.size.height
frame.origin.y -= UIApplication.shared.statusBarFrame.size.height
gradient.frame = frame
layer.insertSublayer(gradient, at: 1)
}
}

Background gradient not see correctly in mode landscape

When I put the device in mode landscape, the background is set wrong. How do I fix it? I also could use to know how to put the entire application in landscape mode.
override func viewDidLoad() {
super.viewDidLoad()
//BACKGROUND FONDO
//A linear Gradient Consists of two colours: top colour and bottom colour
let topColor = UIColor(red: 15.0/255.0, green: 118.0/255.0, blue: 128.0/255.0, alpha: 1.0)
let bottomColor = UIColor(red: 84.0/255.0, green: 187.0/255.0, blue: 187.0/255.0, alpha: 1.0) 
//Add the top and bottom colours to an array and setup the location of those two.
let gradientColors: [CGColor] = [topColor.CGColor, bottomColor.CGColor]
let gradientLocations: [CGFloat] = [0.0, 1.0]
//Create a Gradient CA layer
let gradientLayer: CAGradientLayer = CAGradientLayer()
gradientLayer.colors = gradientColors
gradientLayer.locations = gradientLocations
gradientLayer.frame = self.view.bounds
self.view.layer.insertSublayer(gradientLayer, atIndex: 0)
} //FIN BACKGROUND GRADIENT
In your case gradient is a Layer, not View. It means that it will not resize or change position while the containing layer changes (e.g. during rotation). You have to change the frame of the sublayer manually during rotation.
var gradientLayer = CAGradientLayer()
override func viewDidLoad() {
super.viewDidLoad()
createGredientBackground()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
gradientLayer.frame = view.layer.bounds
}
func createGredientBackground() {
let colorTop = UIColor(red: 255.0/255.0, green: 149.0/255.0, blue: 0.0/255.0, alpha: 1.0).cgColor
let colorBottom = UIColor(red: 255.0/255.0, green: 94.0/255.0, blue: 58.0/255.0, alpha: 1.0).cgColor
gradientLayer.colors = [colorTop, colorBottom]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.frame = self.view.bounds
self.view.layer.insertSublayer(gradientLayer, at:0)
}

Xcode 6 gradient layer to resize correctly with object resizing a universal app

Is there a preferred method to add an auto-resizing gradient layer to a UIView or UILabel that will conform to the autolayout constraints set in IB (Xcode 6) and a wAny hAny canvass, using Swift?
For example, I can start with two labels that span universal widths using constraints using an wAny and hAny canvass, but when running the app on an iPad2 simulator, the labels size correctly but not the gradient layer.
I've looked into willAnimateRotationToInterfaceOrientation and adding a notification based on UIDeviceOrientationDidChangeNotification, but this is not helpful when the app is loaded in landscape (although once rotated the layers are redrawn and do match).
The only solution I have found is to manally adjust the width of each button to conform to the maximum view width of each (iPad2 in landscape) in the Utilities side panel in Xcode, but that creates "misplaced view" warnings and a messy canvass.
Any other ideas?
Here is what I have:
In viewDidLoad, I send each button through this:
Example:
//viewDidLoad --
self.configButton(self.button1!)
self.configButton(self.button2!)
self.configButton(self.button3!)
//
func configButton(theButton: UIButton){
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame.size = theButton.frame.size
theButton.layer.insertSublayer(gradient, atIndex: 0)
let topR = CGFloat(13.0)
let topG = CGFloat(55.0)
let topB = CGFloat(112.0)
let bottomR = CGFloat(90.0)
let bottomG = CGFloat(126.0)
let bottomB = CGFloat(167.0)
let colorTop = UIColor(red: CGFloat(topR/255.0), green: CGFloat(topG/255.0), blue: CGFloat(topB/255.0), alpha: 1.0).CGColor
let colorBottom = UIColor(red: CGFloat(bottomR/255.0), green: CGFloat(bottomR/255.0), blue: CGFloat(bottomR/255.0), alpha: 1.0).CGColor
let gradientColors: [AnyObject] = [colorTop, colorBottom]
gradient.colors = gradientColors
gradient.locations = [0.0 , 1.0]
gradient.startPoint = CGPoint(x: 0.5, y: 0.0)
gradient.endPoint = CGPoint(x: 0.5, y: 1.0)
let theCornerRadius: CGFloat = 5.0
theButton.layer.cornerRadius = theCornerRadius
theButton.layer.masksToBounds = true
theButton.layer.borderWidth = 0.1
theButton.setTitleColor(UIColor.whiteColor(), forState: UIControlState.Normal)
}
I would do this by subclassing UIButton, and putting the configureButton method, minus the layer sizing line, in that class. You can then set the frame of the gradient layer equal to the bounds of its super layer (the button's default layer) in layoutSublayersOfLayer because that layer automatically resizes to keep it the same size as the button. You don't need any code in the controller with this approach.
class RDButton: UIButton {
let gradient = CAGradientLayer()
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configureButton()
}
func configureButton() {
self.layer.insertSublayer(gradient, atIndex: 0)
let topR = CGFloat(13.0)
let topG = CGFloat(55.0)
let topB = CGFloat(112.0)
let bottomR = CGFloat(90.0)
let bottomG = CGFloat(126.0)
let bottomB = CGFloat(167.0)
let colorTop = UIColor(red: CGFloat(topR/255.0), green: CGFloat(topG/255.0), blue: CGFloat(topB/255.0), alpha: 1.0).CGColor
let colorBottom = UIColor(red: CGFloat(bottomR/255.0), green: CGFloat(bottomR/255.0), blue: CGFloat(bottomR/255.0), alpha: 1.0).CGColor
let gradientColors: [AnyObject] = [colorTop, colorBottom]
gradient.colors = gradientColors
gradient.locations = [0.0 , 1.0]
gradient.startPoint = CGPoint(x: 0.5, y: 0.0)
gradient.endPoint = CGPoint(x: 0.5, y: 1.0)
let theCornerRadius: CGFloat = 5.0
self.layer.cornerRadius = theCornerRadius
self.layer.masksToBounds = true
self.layer.borderWidth = 0.1
self.setTitleColor(UIColor.whiteColor(), forState: UIControlState.Normal)
}
override func layoutSublayersOfLayer(layer: CALayer!) {
super.layoutSublayersOfLayer(layer)
gradient.frame = layer.bounds
}
}
Here is what I came up with to pass gradient colors to this new button class:
import Foundation
import UIKit
import QuartzCore
class RDButton: UIButton{
let gradient = CAGradientLayer()
var gradientColors: [Int] = []
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func configureButton(thecolors: [Int]) {
println("thecolors: \(thecolors)")
self.layer.insertSublayer(gradient, atIndex: 0)
let topR = CGFloat(thecolors[0])
let topG = CGFloat(thecolors[1])
let topB = CGFloat(thecolors[2])
let bottomR = CGFloat(thecolors[3])
let bottomG = CGFloat(thecolors[4])
let bottomB = CGFloat(thecolors[5])
let colorTop = UIColor(red: CGFloat(topR/255.0), green: CGFloat(topG/255.0), blue: CGFloat(topB/255.0), alpha: 1.0).CGColor
let colorBottom = UIColor(red: CGFloat(bottomR/255.0), green: CGFloat(bottomR/255.0), blue: CGFloat(bottomR/255.0), alpha: 1.0).CGColor
let gradientColors: [AnyObject] = [colorTop, colorBottom]
gradient.colors = gradientColors
gradient.locations = [0.0 , 1.0]
gradient.startPoint = CGPoint(x: 0.5, y: 0.0)
gradient.endPoint = CGPoint(x: 0.5, y: 1.0)
let theCornerRadius: CGFloat = 5.0
self.layer.cornerRadius = theCornerRadius
self.layer.masksToBounds = true
self.layer.borderWidth = 0.1
self.setTitleColor(UIColor.whiteColor(), forState: UIControlState.Normal)
}
override func layoutSublayersOfLayer(layer: CALayer!) {
super.layoutSublayersOfLayer(layer)
gradient.frame = layer.bounds
}
}
In the View Controller, I did this:
#IBOutlet weak var mySweetButton: RDButton?
// etc for each button
let buttonsgradient = [13,55,112,90,126,167]
override func viewDidLoad(){
super.viewDidLoad()
//...
mySweetButton?.configureButton(buttonsgradient)
/// etc for each button
// ...
}
Probably not the optimal way of doing this ...

Resources