I have a table view. I create my cells using xib files.
I need 1 cell(which is the current user) to be a gradient background, I created a background gradient layer but it does not fit to view. After I pull list down, it works.
You can see screenshots i have and this is my code
//In the table cell,
if data.userUniqueId != nil && userUIID != nil && data.userUniqueId! == userUIID! {
let startColor = UIColor(red: 253/255.0, green: 162/255.0, blue: 75/255.0, alpha: 1)
let endColor = UIColor(red:251/255.0, green:215/255.0, blue: 126/255.0, alpha:1)
self.setGradient(colors: [startColor.cgColor, endColor.cgColor],angle: 90)
self.layer.borderColor = UIColor.white.cgColor
self.layer.borderWidth = 2
number.textColor = UIColor.white
}
And this is gradient method
func setGradient(colors: [CGColor], angle: Float = 0) {
let gradientLayerView: UIView = UIView(frame: CGRect(x:0, y: 0, width: bounds.width, height: bounds.height))
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = gradientLayerView.bounds
gradient.colors = colors
let alpha: Float = angle / 360
let startPointX = powf(
sinf(2 * Float.pi * ((alpha + 0.75) / 2)),
2
)
let startPointY = powf(
sinf(2 * Float.pi * ((alpha + 0) / 2)),
2
)
let endPointX = powf(
sinf(2 * Float.pi * ((alpha + 0.25) / 2)),
2
)
let endPointY = powf(
sinf(2 * Float.pi * ((alpha + 0.5) / 2)),
2
)
gradient.endPoint = CGPoint(x: CGFloat(endPointX),y: CGFloat(endPointY))
gradient.startPoint = CGPoint(x: CGFloat(startPointX), y: CGFloat(startPointY))
gradientLayerView.layer.insertSublayer(gradient, at: 0)
layer.insertSublayer(gradientLayerView.layer, at: 0)
}
And also how can I remove gradient from layer for next usage of cells?
EDIT:
Table View Codes
extension RecordsViewController : UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return values.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "ScoreBoardCell", for: indexPath) as? ScoreBoardCell {
let score = self.values[indexPath.row]
cell.setData(indexPath.row, score)
cell.selectionStyle = .none
return cell
}
return UITableViewCell()
}
}
The problem is this line:
gradient.frame = gradientLayerView.bounds
At the time this code runs in cellForRowAt, your gradientLayerView has not yet been fully laid out, so its bounds are not yet known. Thus, you are saddling your gradient with a frame which will not fit its view after layout does take place.
I think you can solve the problem by moving the call to cell.setData to tableView(_:willDisplay:forRowAt:), at which point the cell size should be known.
And also how can I remove gradient from layer for next usage of cells?
Well, the problem here is that your condition has no alternative:
if data.userUniqueId != nil && userUIID != nil && data.userUniqueId! == userUIID!
The question is: what if not? In that case, if the gradient layer has been added, it needs to be removed. You have provided no logic for that situation. — Also you need to be careful because what if the gradient layer has been added and your logic now causes you to add another gradient layer? Basically your whole approach fails to take account of the most fundamental fact about table view cells, namely that they are reused.
[Xcode 12 Swift 5.2]
After looking for hours nothing worked for me. Changing gradient layer's bounds in layoutSubviews() or setting a gradient layer in willDisplayCell().
so I have created a gradient layer and created an UIImage of it and added that image as background. and that worked like charm here is a sweet and simple extension to convert the Gradient layer into UIImage.
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
}
Code for making a CAGradientLayer :
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [
UIColor.init().Hex(hexString: "#EB0089").cgColor,
UIColor.init().Hex(hexString: "#FD2E59").cgColor
]
gradientLayer.cornerRadius = self.layer.cornerRadius
gradientLayer.startPoint = CGPoint(x: 0, y: 1)
gradientLayer.endPoint = CGPoint(x: 0, y: 0)
gradientLayer.frame = Button1.bounds
let GradientImage = UIImage().getImageFrom(gradientLayer: gradientLayer)
set GradientImage wherever you wanted.
You need to call this inside
var once = true
override func layoutSubviews() {
super.layoutSubviews()
if once {
let startColor = UIColor(red: 253/255.0, green: 162/255.0, blue: 75/255.0, alpha: 1)
let endColor = UIColor(red:251/255.0, green:215/255.0, blue: 126/255.0, alpha:1)
self.setGradient(colors: [startColor.cgColor, endColor.cgColor],angle: 90)
once = false
}
}
Where the cell bounds are correctly rendered , When you want to remove
cell.contentView.subviews {
if $0.tag == 200 {
$0.removeFromSuperview()
}
}
And give it a tag
let gradientLayerView: UIView = UIView(frame: CGRect(x:0, y: 0, width: bounds.width, height: bounds.height))
gradientLayerView.tag = 200
When you add anything to cell don't froget tp add it to contentView
contentView.addSubview(gradientLayerView)
Related
How can I customize a tableView cell to have a gradient shadow across the bottom of the cell similar to the orange tableView cells in the Health App.
I would like to make it as close to this as possible.
This is what I currently have, I've added the separation between the cells by using a white border and added a radius of 8 for the corners.
What can I add to make the gradient?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier: String = "stockCell"
let myCell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)!
myCell.textLabel?.textAlignment = .center
myCell.textLabel?.font = .boldSystemFont(ofSize: 18)
myCell.textLabel?.textColor = UIColor.white
myCell.layer.backgroundColor = UIColor(red:0.86, green:0.25, blue:0.25, alpha:1.0).cgColor
let item: StockModel = feedItems[indexPath.row] as! StockModel
myCell.layer.cornerRadius = 8.0
myCell.layer.masksToBounds = true
myCell.layer.borderColor = UIColor.white.cgColor
myCell.layer.borderWidth = 5.0
myCell.layer.cornerRadius = 20
myCell.clipsToBounds = true
}
I believe instead of casting a "gradient shadow" across the bottom of the cell, you just want the cell to have a background gradient from a lighter color on the top to a darker color in the bottom.
I usually use en extension of UIView for adding gradients to views:
extension UIView {
func setGradient(colors: [CGColor]) {
let gradient = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colors
self.layer.insertSublayer(gradient, at: 0)
}
}
Then, try using something like this in your code:
myCell.setGradient([cgColorLight, cgColorDark])
This sometimes doesn't work if you do some custom drawing in your view's layers, but I believe it should be okay for your needs.
You can do both horizontal and vertical gradients with the following but define the following variables:
typealias GradientPoints = (startPoint: CGPoint, endPoint: CGPoint)
enum GradientOrientation {
case topRightBottomLeft
case topLeftBottomRight
case horizontal
case vertical
var startPoint: CGPoint {
return points.startPoint
}
var endPoint: CGPoint {
return points.endPoint
}
var points: GradientPoints {
switch self {
case .topRightBottomLeft:
return (CGPoint.init(x: 0.0, y: 1.0), CGPoint.init(x: 1.0, y: 0.0))
case .topLeftBottomRight:
return (CGPoint.init(x: 0.0, y: 0.0), CGPoint.init(x: 1, y: 1))
case .horizontal:
return (CGPoint.init(x: 0.0, y: 0.5), CGPoint.init(x: 1.0, y: 0.5))
case .vertical:
return (CGPoint.init(x: 0.0, y: 0.0), CGPoint.init(x: 0.0, y: 1.0))
}
}
}
And create an extension for uiview as follows:
extension UIView{
func applyGradient(withColours colours: [UIColor], gradientOrientation orientation: GradientOrientation) {
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map { $0.cgColor }
gradient.startPoint = orientation.startPoint
gradient.endPoint = orientation.endPoint
self.layer.insertSublayer(gradient, at: 0)
}
}
Please add these lines in your cell class.
let gradient = CAGradientLayer()
gradient.frame = self.view.bounds;
gradient.startPoint = CGPoint(x: 0, y: 0)
gradient.endPoint = CGPoint(x: 0, y: 1)
gradient.colors = [UIColor.green.cgColor, UIColor.white.cgColor];
self.view.layer.insertSublayer(gradient, at: 0)
I am using the below code to add a dashed custom bottom border to tableview cells. It is now overlapping with content randomly. Sometimes, the border is not getting displayed.
class AppTableCell: UITableViewCell {
var shapeLayer: CAShapeLayer?
var isBorderAdded = false
func isBottomBorderAdded() -> Bool {
return isBorderAdded
}
func getBottomBorderShapeLayer() -> CAShapeLayer {
return self.shapeLayer!
}
func setBottomBorderShapedLayer(_ layer: CAShapeLayer) {
self.shapeLayer = layer
}
}
The extend tableview cell from the above class and calls the below function in func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell method.
func addDashedBottomBorder(to cell: AppTableCell) {
let color = UIColor.init(red: 191/255, green: 191/255, blue: 191/255, alpha: 1.0).cgColor
let shapeLayer:CAShapeLayer = CAShapeLayer()
let frameSize = cell.frame.size
let shapeRect = CGRect(x: 0, y: 0, width: frameSize.width, height: 0)
shapeLayer.bounds = shapeRect
shapeLayer.position = CGPoint(x: frameSize.width/2, y: frameSize.height)
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = color
shapeLayer.lineWidth = 2.0
shapeLayer.lineJoin = CAShapeLayerLineJoin.round
shapeLayer.lineDashPhase = 3.0
shapeLayer.lineDashPattern = [9,6]
shapeLayer.path = UIBezierPath(roundedRect: CGRect(x: 0, y: shapeRect.height, width: shapeRect.width, height: 0), cornerRadius: 0).cgPath
if (cell.isBorderAdded) {
cell.shapeLayer!.removeFromSuperlayer()
}
cell.shapeLayer = shapeLayer
cell.isBorderAdded = true
cell.layer.addSublayer(shapeLayer)
}
How to display dashed bottom border at the end of each cell properly?
Your are adding a sublayer in your cell's layer with a fixed position:
shapeLayer.position = CGPoint(x: frameSize.width/2, y: frameSize.height)
[...]
cell.layer.addSublayer(shapeLayer)
So it doesn't stick to your cell's bottom side.
You could update this layer's position each time the cell's height change, but my suggestion from a performance and simplicity point of view would be use autolayout.
Add a subview at the bottom of your cell, add the constraints to make it stick, and add only once (in awakeFromNib for example if it's designed in IB) the dashedLayer inside.
Xcode 10 beta 5, iOS 12.0 16A5339e
I use the UICollectionView inside my app with the following code:
extension MainWindowViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return itemMenuArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let itemCell = collectionView.dequeueReusableCell(withReuseIdentifier: "main_menu_cell", for: indexPath) as? MainWindowCollectionViewCell {
let item = itemMenuArray[indexPath.row]
itemCell.refresh(item)
return itemCell
}
return UICollectionViewCell()
}
And the problem is that method .refresh executes for separate cells when I scroll my CollectionView. So the cell that is out of the screen updates every time I scroll into its direction and causes wrong cell colors sometimes. Are there any ideas how to fix that?
.refresh code (I use that in my custom cell class):
public func refresh(_ menu: Menu) {
Title.text = menu.name
if let image = menu.img {
Image.image = UIImage(named: image)
}
print(SubscriptionTitle.text!)
self.setupCellAppearance(firstColor: UIColor(hexString: (menu.firstColor)!), secondColor: UIColor(hexString: (menu.secondColor)!))
}
setupCellAppearance code:
func setupCellAppearance(firstColor: UIColor, secondColor: UIColor) {
// Setting up the gradient for each cell
clipsToBounds = true
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [firstColor.cgColor, secondColor.cgColor]
gradientLayer.frame = self.bounds
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 1, y: 0)
gradientLayer.cornerRadius = 10.0
self.layer.insertSublayer(gradientLayer, at: 0)
// Setting up the corner radius and shadows for each cell
self.layer.shadowColor = firstColor.cgColor
self.layer.shadowOffset = CGSize(width: 0.0, height: 5.0)
self.layer.shadowRadius = 10
self.layer.shadowOpacity = 0.5
self.layer.masksToBounds = false;
self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.layer.cornerRadius).cgPath
}
What happening is cellForItemAt reuse the cell so every time cell is reuse which might already having gradient layer with it and you are trying add a new gradient so that creating a different color. So you need to remove the layer if its already there, to remove layer set name property of CAGradientLayer with some unique name and then before inserting in layer check layer with that name already exist, if it exist remove it.
func setupCellAppearance(firstColor: UIColor, secondColor: UIColor) {
//Check gradient is already exist
for layer in self.layer.sublayers ?? [] {
if layer.name == "BGGradient" {
layer.removeFromSuperlayer()
break
}
}
// Setting up the gradient for each cell
clipsToBounds = true
let gradientLayer = CAGradientLayer()
//set name property of gradient layer
gradientLayer.name = "BGGradient"
gradientLayer.colors = [firstColor.cgColor, secondColor.cgColor]
gradientLayer.frame = self.bounds
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 1, y: 0)
gradientLayer.cornerRadius = 10.0
self.layer.insertSublayer(gradientLayer, at: 0)
// Setting up the corner radius and shadows for each cell
self.layer.shadowColor = firstColor.cgColor
self.layer.shadowOffset = CGSize(width: 0.0, height: 5.0)
self.layer.shadowRadius = 10
self.layer.shadowOpacity = 0.5
self.layer.masksToBounds = false;
self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.layer.cornerRadius).cgPath
}
Right now, I have a custom graph class which creates bar charts and displays them. However they only appear after I appear on the view controller twice. For eg. here I am on one of the view controllers that has the graph but it shows in an incomplete manner.
Then here I am at the same view controller but with the graph how it is meant to look like. As you can see, the bars have gotten longer and the dashed lines have appeared.
To achieve this, all I did was click on one of the tabs in the bottom and then clicked back on the tab for report and it changed the view. I really have no idea why this is happening and have been trying to tackle this for 2 days now. If someone can help me it would be great. Here is the code for a sample viewcontroller that shows the graph.
override func viewDidLoad() {
self.tabBarController?.tabBar.barTintColor = UIColor(red:0.18, green:0.21, blue:0.25, alpha:1.0)
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
self.tabBarController?.tabBar.barTintColor = UIColor(red:0.18, green:0.21, blue:0.25, alpha:1.0)
tupleArray.removeAll()
newTuple.removeAll()
filteredCategories.removeAll()
categories.removeAll()
self.categories = CoreDataHelper.retrieveCategories()
tableView.tableFooterView = UIView(frame: .zero)
tableView.tableFooterView?.isHidden = true
tableView.backgroundColor = UIColor.clear
tableView.layoutMargins = UIEdgeInsets.zero
tableView.separatorInset = UIEdgeInsets.zero
tableView.separatorStyle = UITableViewCellSeparatorStyle.singleLine
UIColor(red:0.40, green:0.43, blue:0.48, alpha:1.0)
currentYear1 = Calendar.current.date(byAdding: .year, value: -1, to: Date())!
currentYear = String(describing: Calendar.current.component(.year, from: currentYear1))
yearLabel.text = "\(currentYear)"
if ExpensesAdditions().yearHasExpense(year: currentYear){
noExpenseLabel.isHidden = true
}
else{
tableView.isHidden = true
barChartView.isHidden = true
noExpenseLabel.isHidden = false
totalSpentLabel.isHidden = true
}
totalSpent = ExpensesAdditions().retrieveYearlySum(year:currentYear)
totalSpentLabel.text = "Total Spent: " + ExpensesAdditions().convertToMoney(totalSpent)
for category in categories{
if ExpensesAdditions().categoryinYearHasExpense(year:currentYear,category:category.title!){
filteredCategories.append(category)
tupleArray.append((category.title!,ExpensesAdditions().retrieveCategoryExpenseForYear(category: category.title!, year: currentYear)))
}
else{}
}
newTuple = tupleArray.sorted(by: { $0.1 > $1.1 })
if ExpensesAdditions().yearHasExpense(year: currentYear){
let dataEntries = generateDataEntries()
barChartView.dataEntries = dataEntries
}
}
override func viewDidAppear(_ animated: Bool) {
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredCategories.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "lastYearAnalysisCell") as! LastYearAnalysisViewTableViewCell
cell.isUserInteractionEnabled = false
let row = indexPath.row
let percentage:Double = (newTuple[row].1/totalSpent)*100
let percentageDisplay:Int = Int(percentage.rounded())
cell.nameLabel.text = newTuple[row].0
cell.amountLabel.text = "(\(percentageDisplay)"+"%) "+String(describing: UserDefaults.standard.value(forKey: "chosenCurrencySymbol")!)+ExpensesAdditions().convertToMoney(newTuple[row].1)
return cell
}
func generateDataEntries() -> [BarEntry] {
let colors = [#colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1), #colorLiteral(red: 0.2392156869, green: 0.6745098233, blue: 0.9686274529, alpha: 1), #colorLiteral(red: 0.9607843161, green: 0.7058823705, blue: 0.200000003, alpha: 1), #colorLiteral(red: 0.9372549057, green: 0.3490196168, blue: 0.1921568662, alpha: 1), #colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1), #colorLiteral(red: 0.3647058904, green: 0.06666667014, blue: 0.9686274529, alpha: 1)]
maximum = newTuple[0].1
var result: [BarEntry] = []
/// ---- Complicated Divising -------//////
var maximumString:String = ExpensesAdditions().convertToMoney(maximum)
var maximumInt:Int = Int(maximumString.getAcronyms())!
let endIndex = maximumString.index(maximumString.endIndex, offsetBy: -3)
let truncated = maximumString.substring(to: endIndex)
if maximumInt < 5{
divisor = Double((5*(pow(10, truncated.count - 1))) as NSNumber)
}
else if maximumInt > 5{
divisor = Double((pow(10, truncated.count)) as NSNumber)
}
for i in 0..<filteredCategories.count {
let value = ExpensesAdditions().convertToMoney(tupleArray[i].1)
var height:Double = Double(value)! / divisor
result.append(BarEntry(color: colors[i % colors.count], height: height, textValue: value, title: filteredCategories[i].title!))
}
return result
//// --- Complicated Divising End -------- /////
}
If you need anymore code, please let me know and thanks in advance!
Here is the code for the bar chart entry code:
import UIKit
class BasicBarChart: UIView {
/// the width of each bar
let barWidth: CGFloat = 40.0
/// space between each bar
let space: CGFloat = 20.0
/// space at the bottom of the bar to show the title
private let bottomSpace: CGFloat = 40.0
/// space at the top of each bar to show the value
private let topSpace: CGFloat = 40.0
/// contain all layers of the chart
private let mainLayer: CALayer = CALayer()
/// contain mainLayer to support scrolling
private let scrollView: UIScrollView = UIScrollView()
var dataEntries: [BarEntry]? = nil {
didSet {
mainLayer.sublayers?.forEach({$0.removeFromSuperlayer()})
if let dataEntries = dataEntries {
scrollView.contentSize = CGSize(width: (barWidth + space)*CGFloat(dataEntries.count), height: self.frame.size.height)
mainLayer.frame = CGRect(x: 0, y: 0, width: scrollView.contentSize.width, height: scrollView.contentSize.height)
drawHorizontalLines()
for i in 0..<dataEntries.count {
showEntry(index: i, entry: dataEntries[i])
}
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
convenience init() {
self.init(frame: CGRect.zero)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
private func setupView() {
scrollView.layer.addSublayer(mainLayer)
self.addSubview(scrollView)
}
override func layoutSubviews() {
scrollView.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height)
}
private func showEntry(index: Int, entry: BarEntry) {
/// Starting x postion of the bar
let xPos: CGFloat = space + CGFloat(index) * (barWidth + space)
/// Starting y postion of the bar
let yPos: CGFloat = translateHeightValueToYPosition(value: Float(entry.height))
drawBar(xPos: xPos, yPos: yPos, color: entry.color)
/// Draw text above the bar
drawTextValue(xPos: xPos - space/2, yPos: yPos - 30, textValue: entry.textValue, color: entry.color)
/// Draw text below the bar
drawTitle(xPos: xPos - space/2, yPos: mainLayer.frame.height - bottomSpace + 10, title: entry.title, color: entry.color)
}
private func drawBar(xPos: CGFloat, yPos: CGFloat, color: UIColor) {
let barLayer = CALayer()
barLayer.frame = CGRect(x: xPos, y: yPos, width: barWidth, height: mainLayer.frame.height - bottomSpace - yPos)
barLayer.backgroundColor = color.cgColor
mainLayer.addSublayer(barLayer)
}
private func drawHorizontalLines() {
self.layer.sublayers?.forEach({
if $0 is CAShapeLayer {
$0.removeFromSuperlayer()
}
})
let horizontalLineInfos = [["value": Float(0.0), "dashed": true], ["value": Float(0.5), "dashed": true], ["value": Float(1.0), "dashed": true]]
for lineInfo in horizontalLineInfos {
let xPos = CGFloat(0.0)
let yPos = translateHeightValueToYPosition(value: (lineInfo["value"] as! Float))
let path = UIBezierPath()
path.move(to: CGPoint(x: xPos, y: yPos))
path.addLine(to: CGPoint(x: scrollView.frame.size.width, y: yPos))
let lineLayer = CAShapeLayer()
lineLayer.path = path.cgPath
lineLayer.lineWidth = 0.5
if lineInfo["dashed"] as! Bool {
lineLayer.lineDashPattern = [4, 4]
}
lineLayer.strokeColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1).cgColor
self.layer.insertSublayer(lineLayer, at: 0)
}
}
private func drawTextValue(xPos: CGFloat, yPos: CGFloat, textValue: String, color: UIColor) {
let textLayer = CATextLayer()
textLayer.frame = CGRect(x: xPos, y: yPos, width: barWidth+space, height: 22)
textLayer.foregroundColor = color.cgColor
textLayer.backgroundColor = UIColor.clear.cgColor
textLayer.alignmentMode = kCAAlignmentCenter
textLayer.contentsScale = UIScreen.main.scale
textLayer.font = CTFontCreateWithName(UIFont.systemFont(ofSize: 0).fontName as CFString, 0, nil)
textLayer.fontSize = 14
textLayer.string = textValue
mainLayer.addSublayer(textLayer)
}
private func drawTitle(xPos: CGFloat, yPos: CGFloat, title: String, color: UIColor) {
let textLayer = CATextLayer()
textLayer.frame = CGRect(x: xPos, y: yPos, width: barWidth + space, height: 22)
textLayer.foregroundColor = color.cgColor
textLayer.backgroundColor = UIColor.clear.cgColor
textLayer.alignmentMode = kCAAlignmentCenter
textLayer.contentsScale = UIScreen.main.scale
textLayer.font = CTFontCreateWithName(UIFont.systemFont(ofSize: 0).fontName as CFString, 0, nil)
textLayer.fontSize = 14
textLayer.string = title
mainLayer.addSublayer(textLayer)
}
private func translateHeightValueToYPosition(value: Float) -> CGFloat {
let height: CGFloat = CGFloat(value) * (mainLayer.frame.height - bottomSpace - topSpace)
return mainLayer.frame.height - bottomSpace - height
}
}
The problem is most likely that you set up your bar chart in viewWillAppear, at which point the final size of the view has not been set yet. To fix this you must either delay setting the data until in viewDidAppear, or you need to trigger a redraw of the bars and dashed lines when the frame size has changed i.e. from layoutSubviews.
One point of advice I want to share with you is that you should try to separate your code so that sizing and positioning of all the view's sub-items are done in layoutSubviews, and the actual adding and removal of them are done elsewhere.
Update:
In your class BasicBarChart you should try to separate adding the chart elements, and do the actual sizing of the elements. This is because sizing is an event that often occurs more than once, especially when using auto-layout.
If your BasicBarChart view is set up with constraints, it will usually receive at least two different sizes during setup, once with the sizes in your storyboard file, and once with the actual device derived sizes. Since you don't want to add everything more than once, you should do the adding elsewhere. A good place could be during initialization or when setting data.
The sizing of a view's sub-elements is best done in its layoutSubviews() function. You have already started! Just move over the sizing of your other elements here too, and everything should work much better.
class BasicBarChart: UIView {
override func layoutSubviews() {
scrollView.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height)
// Size and position bars, labels and dashed lines here
}
}
I am new to programming and I have no idea how I can fill a undefined geometrical form with a gradient color...
I managed to do with a simple color like that:
func fillRegion(pixelX: Int, pixelY: Int, withColor color: UIColor) {
var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0
color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
var newColor = (UInt32)(alpha*255)<<24 | (UInt32)(red*255)<<16 | (UInt32)(green*255)<<8 | (UInt32)(blue*255)<<0
let pixelColor = regionsData.advanced(by: (pixelY * imageHeight) + pixelX).pointee
if pixelColor == blackColor { return }
var pointerRegionsData: UnsafeMutablePointer<UInt32> = regionsData
var pointerImageData: UnsafeMutablePointer<UInt32> = imageData
var pixelsChanged = false
for i in 0...(imageHeight * imageHeight - 1) {
if pointerRegionsData.pointee == pixelColor {
pointerImageData = imageData.advanced(by: i)
if pointerImageData.pointee != newColor {
// newColor = newColor + 1
pointerImageData.pointee = newColor
pixelsChanged = true
}
}
pointerRegionsData = pointerRegionsData.successor()
}
if pixelsChanged {
self.image = UIImage(cgImage: imageContext.makeImage()!)
DispatchQueue.main.async {
CATransaction.setDisableActions(true)
self.layer.contents = self.image.cgImage
self.onImageDraw?(self.image)
}
self.playTapSound()
}
}
Pixel by pixel it fill the color (ignoring the black color) any ideas how to do that with Gradient color? thanks!
You can make a gradient layer and apply an image or a shape layer as its mask. Here is a playground.
import PlaygroundSupport
import UIKit
class V: UIView {
private lazy var gradientLayer: CAGradientLayer = {
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [UIColor.red.cgColor,
UIColor.purple.cgColor,
UIColor.blue.cgColor,
UIColor.white.cgColor]
gradientLayer.locations = [0, 0.3, 0.9, 1]
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 0, y: 1)
gradientLayer.mask = self.strokeLayer
self.layer.addSublayer(gradientLayer)
return gradientLayer
}()
private lazy var strokeLayer: CAShapeLayer = {
let strokeLayer = CAShapeLayer()
strokeLayer.path = UIBezierPath(ovalIn: CGRect(x:0, y: 0, width: 100, height: 100)).cgPath
return strokeLayer
}()
override func layoutSubviews() {
super.layoutSubviews()
strokeLayer.path = UIBezierPath(ovalIn: bounds).cgPath
gradientLayer.frame = bounds
layer.addSublayer(gradientLayer)
}
}
let v = V(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
PlaygroundPage.current.liveView = v
I'm not 100% sure I understand the question, but it seems like you want to fill any-old shape with a gradient, right? If so, there are a couple of ways to do that, but the easiest is to make a gradient that's the same size as the boundary of the shape and then apply that as its color. I'm typing this on my PC so I'm sure there's syntax errors, but here goes...
let size = CGSize(width, height)
UIGraphicsRenderer(size, false, 0) // I KNOW I have this one wrong
let colors = [tColour.cgColor, bColour.cgColor] as CFArray
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors , locations: nil)
Set the colors array as needed and then send that into the UIImage. You can use locations: to change the orientation.