Add third party ActivityRing to UIcalenderView Decoration - swift

Im trying to show activity rings like Fitness app by apple and using a third party library called ActivityRings. This implementation is inside a UICollectionViewCell.
So far im not able to see anything. I will be sending whole code as last time I posted question related to it, it got no views. So far I just want to see those rings like Fitness app but later will add code to show data from database. This is my whole UICollectionViewCell class:
class MonthlyLogsCollectionViewCell: UICollectionViewCell {
static let reuseIdentifier = "monthly-summary-item-reuse-identifier"
private let activityRing: ActivityRingView = {
let view = ActivityRingView()
view.startColor = UIColor(red: 0.035, green: 0.776, blue: 0.976, alpha: 1.000)
view.endColor = UIColor(red: 0.016, green: 0.365, blue: 0.914, alpha: 1.000)
return view
}()
lazy var calendarView: UICalendarView = {
let calenderView = UICalendarView()
calenderView.calendar = Calendar(identifier: .gregorian)
calenderView.fontDesign = .default
return calenderView
}()
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
func configure(with data: WaterActivity) {
activityRing.animateProgress(to: data.currentItem / data.totalItems, withDuration: 0.5)
}
required init?(coder: NSCoder) {
fatalError("Not implemented")
}
}
// Helper Methods
extension MonthlyLogsCollectionViewCell {
private func configure() {
contentView.backgroundColor = .clear
contentView.addSubview(calendarView)
let currentDate = Date()
let fromDateComponents = DateComponents(calendar: Calendar(identifier: .gregorian), year: 2022, month: 1, day: 1)
guard let fromDate = fromDateComponents.date else {
return
}
let calendarViewDateRange = DateInterval(start: fromDate, end: currentDate)
calendarView.availableDateRange = calendarViewDateRange
calendarView.delegate = self
setupConstraints()
}
private func setupConstraints() {
calendarView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
calendarView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
calendarView.topAnchor.constraint(equalTo: contentView.topAnchor),
calendarView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
calendarView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
])
}
private func addActivityRing() -> ActivityRingView {
activityRing.progress = 1.0
activityRing.ringWidth = UIScreen.main.scale < UIScreen.main.nativeScale ? 5 : 8
activityRing.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
return activityRing
}
}
// UICalenderView Delegate
extension MonthlyLogsCollectionViewCell: UICalendarViewDelegate {
func calendarView(_ calendarView: UICalendarView, decorationFor dateComponents: DateComponents) -> UICalendarView.Decoration? {
return .customView { [unowned self] in
return self.addActivityRing()
}
}
}
Can anyone tell why I cannot see the rings? To my understanding, I should see activity rings with full circle as activityRing.progress = 1.0 which shows full circle as progress = 100%. I tested this library with same code and it works so the only issue should be in addActivityRing().

Related

Why does this logic work well (UICollectionViewCell + CustomPaddingLabel)

When I make a message UI, need a dynamic height UICollectionViewCell which be calculated with label's height. But first try the cell when reuse, customLabel's layout did not change so the cell is maintained cell's layout which previously used.
I found that the problem is CustomPaddingLabel and make it working well. But I still can't understand why this logic make working well. Read layout life cylcle, UICollectionView life cycle, but that can't solve this question.
plz, explain why this logic can make working well, also why CustomPaddingLabel cause this problem. (When I try with UILabel, this problem does not occured)
This is UICollectionViewCell...
final class MessageCell: UICollectionViewCell {
static let identifier: String = .init(describing: MessageCell.self)
private let messageLabel: PaddingLabel = .init().then {
$0.numberOfLines = 0
$0.textColor = .black
$0.font = .systemFont(ofSize: 14.5)
$0.lineBreakStrategy = .pushOut
$0.layer.cornerRadius = 14
$0.clipsToBounds = true
$0.setContentHuggingPriority(.defaultLow, for: .horizontal)
$0.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
}
private let dateLabel = UILabel().then {
$0.font = .systemFont(ofSize: 10, weight: .regular)
$0.textColor = .secondaryLabel
$0.numberOfLines = 1
}
private var chatType: ChatType = .none
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubviews(messageLabel, dateLabel)
}
#available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("Does not use this initializer")
}
override func layoutSubviews() {
super.layoutSubviews()
}
override func prepareForReuse() {
super.prepareForReuse()
messageLabel.text = nil
dateLabel.text = nil
chatType = .none
self.messageLabel.snp.removeConstraints()
self.dateLabel.snp.removeConstraints()
self.messageLabel.layoutIfNeeded()
}
func setCell(with message: String, type: ChatType, dateString: String) {
self.messageLabel.text = message
self.chatType = type
self.dateLabel.text = dateString
self.configureLayouts()
self.configureProperties()
}
}
private extension MessageCell {
func configureLayouts() {
switch chatType {
case .none:
break
case .send:
messageLabel.snp.remakeConstraints { make in
make.top.bottom.equalToSuperview()
make.trailing.equalToSuperview().inset(20)
make.leading.greaterThanOrEqualToSuperview()
}
dateLabel.snp.remakeConstraints { make in
make.trailing.equalTo(messageLabel.snp.leading).offset(-4)
make.leading.greaterThanOrEqualToSuperview().inset(56)
make.bottom.equalTo(messageLabel)
}
case .receive:
messageLabel.snp.remakeConstraints { make in
make.top.bottom.equalToSuperview()
make.leading.equalToSuperview().inset(56)
make.trailing.lessThanOrEqualToSuperview()
}
dateLabel.snp.remakeConstraints { make in
make.leading.equalTo(messageLabel.snp.trailing).offset(4)
make.trailing.lessThanOrEqualToSuperview().inset(24)
make.bottom.equalTo(messageLabel)
}
}
}
func configureProperties() {
switch chatType {
case .none:
break
case .send:
messageLabel.backgroundColor = UIColor(red: 250/255, green: 230/255, blue: 76/255, alpha: 1)
case .receive:
messageLabel.backgroundColor = .white
}
}
}
and this is CustomPaddingLabel...
class PaddingLabel: UILabel {
private var topInset: CGFloat
private var bottomInset: CGFloat
private var leftInset: CGFloat
private var rightInset: CGFloat
override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
return CGSize(width: size.width + leftInset + rightInset, height: size.height + topInset + bottomInset)
}
override var bounds: CGRect {
didSet {
preferredMaxLayoutWidth = bounds.width - (leftInset + rightInset)
}
}
init(topInset: CGFloat = 8, bottomInset: CGFloat = 8, leftInset: CGFloat = 10, rightInset: CGFloat = 10) {
self.topInset = topInset
self.bottomInset = bottomInset
self.leftInset = leftInset
self.rightInset = rightInset
super.init(frame: .zero)
}
#available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("Does not use this initializer")
}
override func drawText(in rect: CGRect) {
let insets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)
super.drawText(in: rect.inset(by: insets))
}
}
Configure the cell height of the UICollectionVIew to dynamic according to the CustomPaddingLabel's height. But when the cell be reused, CustomPaddingLabel's layout is concreted so the next cell has unbalance fit layout.
I found the problem is from CustomPaddingLabel (use UILabel this problem doesn't happen), and fix it worked well. But I don't know why this problem was occured and why this logic solved that problem.
plz, explain this question.

Subclassing UITextField with other UI elements / Swift 5

Hello everyone!)
Need some help!)
I have custom text field with input limit which was in my view controller. If you look below, you will see that my text field has: UIView (underlayer with some borders), two UILabels (name label and counter label), and UITextField inside of UIView. Now I want to make UITextField subclass and configure my text field there with whole UI-es.
MARK: - I working without storyboards, the code only.
The question is, can I implement this in UITextField class?) Or maybe better to use UIView class?)
I experimented and tried to do it in TextField class, but stuck on UIView (underlayer), I can't make it behind my text field. I add a bit of code.)
Have you any ideas how to implement this in right way?)
Thanks for every answer!)
Example
Code...
UIViewController class
import UIKit
class ViewController: UIViewController {
var inputLimitTextField = InputLimitTextField(frame: CGRect(x: 45, y: 200, width: 300, height: 40))
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(inputLimitTextField)
}
}
UITextField class
import UIKit
class InputLimitTextField: UITextField {
var underlayerView = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
configureTextField()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configureTextField()
}
func configureTextField() {
backgroundColor = .purple
underlayerView.backgroundColor = .red
underlayerView.alpha = 0.5
addSubview(underlayerView)
underlayerView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
underlayerView.topAnchor.constraint(equalTo: self.bottomAnchor),
underlayerView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
underlayerView.centerXAnchor.constraint(equalTo: self.centerXAnchor)
])
}
override func layoutSubviews() {
super.layoutSubviews()
underlayerView.frame = self.bounds
sendSubviewToBack(underlayerView)
}
}
Considering the fact that there is still no answer to my question that would solve this issue… Also, given that using subclasses is a pretty popular practice in programming... I didn't find a specific answer to such a question on the stack. That's why I decided to answer my own question. I hope my approach to solving the problem helps someone in the future...
Code...
UIViewController class...
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
private lazy var inputLimitTextField = InputLimitTextField()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(inputLimitTextField)
inputLimitTextFieldPosition()
}
private func inputLimitTextFieldPosition() {
inputLimitTextField.center.x = self.view.center.x
inputLimitTextField.center.y = self.view.center.y - 100
}
}
UITextField class...
import UIKit
class InputLimitTextField: UITextField, UITextFieldDelegate {
private lazy var nameLabel = UILabel()
private lazy var counterLabel = UILabel()
private let textLayer = CATextLayer()
private let padding = UIEdgeInsets(top: 0.5, left: 10, bottom: 0.5, right: 17)
private let purpleUIColor = UIColor(red: 0.2849253164, green: 0.1806431101, blue: 0.5, alpha: 1.0)
private let purpleCGColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(),
components: [0.2849253164, 0.1806431101, 0.5, 1.0])
private let redUIColor = UIColor(red: 1, green: 0.1806431101, blue: 0.09760022642, alpha: 1)
private let redCGColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(),
components: [ 1, 0.1806431101, 0.09760022642, 1.0])
override init(frame: CGRect) {
super.init(frame: frame)
configureTextField()
configureNameLabel()
configureCunterLabel()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configureTextField()
configureNameLabel()
configureCunterLabel()
}
private func configureTextField() {
let screenRect = UIScreen.main.bounds
let screenWidth = screenRect.size.width - 25
let textFieldFrame = CGRect(x: 0, y: 0, width: screenWidth, height: 40)
frame = textFieldFrame
backgroundColor = .clear
textColor = purpleUIColor
font = UIFont(name: "Helvetica", size: 17)
placeholder = "Input limit"
textAlignment = .left
contentVerticalAlignment = .center
clearButtonMode = .always
autocorrectionType = .no
keyboardType = .default
returnKeyType = .done
delegate = self
textLayer.backgroundColor = UIColor.white.cgColor
textLayer.borderColor = purpleCGColor
textLayer.borderWidth = 1.2
textLayer.cornerRadius = 10
textLayer.frame = layer.bounds
layer.insertSublayer(textLayer, at: 0)
layer.shadowColor = .init(gray: 0.5, alpha: 0.5)
layer.shadowOpacity = 0.7
layer.shadowOffset = .init(width: 2, height: 2)
addSubview(nameLabel)
nameLabel.frame = CGRect(x: 12, y: -12, width: 55, height: 16)
addSubview(counterLabel)
counterLabel.frame = CGRect(x: screenWidth - 34, y: 9, width: 22, height: 22)
}
override internal func textRect(forBounds bounds: CGRect) -> CGRect {
let bounds = super.textRect(forBounds: bounds)
return bounds.inset(by: padding)
}
override internal func editingRect(forBounds bounds: CGRect) -> CGRect {
let bounds = super.editingRect(forBounds: bounds)
return bounds.inset(by: padding)
}
override internal func clearButtonRect(forBounds bounds: CGRect) -> CGRect {
let screenRect = UIScreen.main.bounds
let screenWidth = screenRect.size.width - 25
return CGRect(x: screenWidth - 70, y: 0, width: 40, height: 40)
}
private func enableUI() {
self.textLayer.borderColor = redCGColor
self.counterLabel.layer.borderColor = redCGColor
self.counterLabel.textColor = redUIColor
self.textColor = redUIColor
self.nameLabel.layer.borderColor = redCGColor
self.nameLabel.textColor = redUIColor
}
private func disableUI() {
self.textLayer.borderColor = purpleCGColor
self.counterLabel.layer.borderColor = purpleCGColor
self.counterLabel.textColor = purpleUIColor
self.textColor = purpleUIColor
self.nameLabel.layer.borderColor = purpleCGColor
self.nameLabel.textColor = purpleUIColor
}
func firstTenCharsColor(text: String) -> NSMutableAttributedString {
let characterCount = 10
let stringLength = text.utf16.count
let attributedString = NSMutableAttributedString(string: text)
if stringLength >= characterCount {
attributedString.addAttribute(.foregroundColor, value: #colorLiteral( red: 0.2849253164, green: 0.1806431101, blue: 0.5, alpha: 1), range: NSMakeRange(0, characterCount) )
}
return attributedString
}
private func updateUI(inputText: String?) {
guard let textCount = inputText?.count else { return }
guard let text = self.text else { return }
if (textCount <= 10){
self.counterLabel.text = "\(10 - textCount)"
disableUI()
} else if (textCount >= 10) {
self.counterLabel.text = "\(10 - textCount)"
enableUI()
self.attributedText = firstTenCharsColor(text: text)
}
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let text = self.text, let textRange = Range(range, in: text) else { return true }
let updatedText = text.replacingCharacters(in: textRange, with: string)
self.updateUI(inputText: updatedText)
return true
}
func textFieldShouldClear(_ textField: UITextField) -> Bool {
self.textLayer.borderColor = purpleCGColor
self.counterLabel.text = "10"
disableUI()
return true
}
private func configureNameLabel() {
nameLabel.backgroundColor = .white
nameLabel.layer.cornerRadius = 3
nameLabel.layer.borderWidth = 1.2
nameLabel.layer.borderColor = purpleCGColor
nameLabel.layer.masksToBounds = true
nameLabel.font = UIFont(name: "Helvetica", size: 11)
nameLabel.text = "Input limit"
nameLabel.textAlignment = .center
nameLabel.textColor = purpleUIColor
}
private func configureCunterLabel() {
counterLabel.backgroundColor = .white
counterLabel.layer.cornerRadius = 5
counterLabel.layer.borderWidth = 1.2
counterLabel.layer.borderColor = purpleCGColor
counterLabel.layer.masksToBounds = true
counterLabel.font = UIFont(name: "Helvetica", size: 12)
counterLabel.text = "10"
counterLabel.textAlignment = .center
counterLabel.textColor = purpleUIColor
}
}
You can use it for any iPhone...
Stay safe and good luck! :)

NSSlider custom subclass - how to maintain the link between the knob position and user interaction?

Trying to create a custom NSSlider. Overriding the drawKnob() method of the NSSliderCell changes the knob's appearance but doing this somehow disconnects the link between the knob's position and user interactions with the slider.
In the objective C example that is often referenced (https://github.com/lucasderraugh/LADSlider) it looks like when you override drawKnob() you then need to explicitly deal with the startTracking method, but I haven't found a solution that works for me - at the moment I am just setting the cell's startTracking 'at' property to the current value of the slider, but not sure what the right approach is.
Quite a few examples include an NSCoder argument in the cell's custom initialiser - I don't understand why, but maybe this has something to do with ensuring a connection between the display of the knob and the actual slider value?
import Cocoa
class ViewController: NSViewController {
var seekSlider = NSSlider()
var seekSliderCell = SeekSliderCell()
override func viewDidLoad() {
super.viewDidLoad()
seekSlider = NSSlider(target: self, action: #selector(self.seek(_:)))
seekSlider.cell = seekSliderCell
seekSlider.cell?.target = self
seekSlider.cell?.action = #selector(self.seek(_:))
seekSlider.isEnabled = true
seekSlider.isContinuous = true
view.addSubview(seekSlider)
}
#objc func seek(_ sender: NSObject) {
let val = seekSlider.cell?.floatValue
let point = NSPoint(x: Double(val!), y: 0.0)
seekSlider.cell?.startTracking(at: point, in: self.seekSlider)
}
}
class SeekSliderCell: NSSliderCell {
// required init(coder aDecoder: NSCoder) {
// super.init(coder: aDecoder)
// }
override func drawKnob() {
let frame = NSRect(x: 0.0, y: 6.0, width: 20.0, height: 10.0)
let c = NSColor(red: 0.9, green: 0.0, blue: 0.6, alpha: 1.0)
c.setFill()
NSBezierPath.init(roundedRect: frame, xRadius: 3, yRadius: 3).fill()
}
override func startTracking(at startPoint: NSPoint, in controlView: NSView) -> Bool {
return true
}
}
The documentation of drawKnob() states:
Special Considerations
If you create a subclass of NSSliderCell, don’t override this method. Override drawKnob(_:) instead.
Instead of
func drawKnob()
override
func drawKnob(_ knobRect: NSRect)
Example:
class ViewController: NSViewController {
var seekSlider = NSSlider()
var seekSliderCell = SeekSliderCell()
override func viewDidLoad() {
super.viewDidLoad()
seekSlider = NSSlider(target: self, action: #selector(self.seek(_:)))
seekSlider.cell = seekSliderCell
seekSlider.cell?.target = self
seekSlider.cell?.action = #selector(self.seek(_:))
seekSlider.isEnabled = true
seekSlider.isContinuous = true
view.addSubview(seekSlider)
}
#objc func seek(_ sender: NSObject) {
let val = seekSlider.cell?.floatValue
print("\(String(describing: val))")
}
}
class SeekSliderCell: NSSliderCell {
override func drawKnob(_ knobRect: NSRect) {
var frame = NSRect(x: 0.0, y: 6.0, width: 20.0, height: 10.0)
frame.origin.x = knobRect.origin.x + (knobRect.size.width - frame.size.width) / 2
frame.origin.y = knobRect.origin.y + (knobRect.size.height - frame.size.height) / 2
let c = NSColor(red: 0.9, green: 0.0, blue: 0.6, alpha: 1.0)
c.setFill()
NSBezierPath.init(roundedRect: frame, xRadius: 3, yRadius: 3).fill()
}
}

Displaying CAShapeLayer in a collection cell

Good afternoon. The problem with the display of CAShapeLayer layers, or rather with the transfer of information there.
Depending on the rating of the film, the color of the circle changes.
At the first load, everything is displayed correctly in the collection. But once you scroll the collection down (10 cells in total) and up again, the occupancy of the circle and the color change. Photo below:
I get the data from the API, pass it to the cell: image, genre, movie title and rating figure. Everything is transmitted correctly and correctly.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PreviewCardCollectionViewCell", for: indexPath) as! PreviewCardCollectionViewCell
cell.configure(name: movies[indexPath.row].title!, genres: movies_genres[indexPath.row][0], partOfPosterURL: movies[indexPath.row].posterPath!, voteAverage: movies[indexPath.row].voteAverage!)
return cell
}
In PreviewCardCollectionViewCell (here is not the whole code, but the one that concerns the problem):
class PreviewCardCollectionViewCell: UICollectionViewCell {
private lazy var circleView: UIView = {
var view = UIView()
view.backgroundColor = .black
view.layer.cornerRadius = chartViewSize/2
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private lazy var chartView: CircleChartView = {
var chart = CircleChartView()
chart.translatesAutoresizingMaskIntoConstraints = false
return chart
}()
override init(frame: CGRect) {
super.init(frame: frame)
//circleView
self.addSubview(circleView)
circleView.heightAnchor.constraint(equalToConstant: chartViewSize).isActive = true
circleView.widthAnchor.constraint(equalToConstant: chartViewSize).isActive = true
circleView.bottomAnchor.constraint(equalTo: previewCardImageView.bottomAnchor, constant: chartViewSize/2).isActive = true
circleView.leadingAnchor.constraint(equalTo: previewCardImageView.leadingAnchor, constant: 10).isActive = true
//chartView
circleView.addSubview(chartView)
chartView.widthAnchor.constraint(equalToConstant: chartViewSize * 0.93).isActive = true
chartView.heightAnchor.constraint(equalToConstant: chartViewSize * 0.93).isActive = true
chartView.centerXAnchor.constraint(equalTo: circleView.centerXAnchor).isActive = true
chartView.centerYAnchor.constraint(equalTo: circleView.centerYAnchor).isActive = true
}
func configure(name: String, genres: String, partOfPosterURL: String, voteAverage: Double) {
//chartView
chartView.percentage = voteAverage
}
And already from the configure() configuration, the rating digit is passed to the UIView: CircleChartView class (here's the whole code).
class CircleChartView: UIView {
var circleLayer = CAShapeLayer()
var progressLayer = CAShapeLayer()
var percentage: Double = 0.0
private var startPoint = CGFloat(-Double.pi / 2)
private var endPoint = CGFloat(3 * Double.pi / 2)
private var halfSize: CGFloat {
get {
return min(bounds.size.width/2, bounds.size.height/2)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func draw(_ rect: CGRect) {
drawCircle(CAlayer: circleLayer, draw: .baseCircle, ratio: 0.0)
drawCircle(CAlayer: progressLayer, draw: .fillProgressCircle, ratio: percentage)
}
private func drawCircle(CAlayer: CAShapeLayer, draw: CircleChartDraw, ratio: Double) {
var circularPath = UIBezierPath()
switch draw {
case .baseCircle:
circularPath = UIBezierPath(
arcCenter: CGPoint(x: halfSize, y: halfSize),
radius: CGFloat(halfSize - (4.0/2)),
startAngle: startPoint,
endAngle: endPoint,
clockwise: true)
case .fillProgressCircle:
circularPath = UIBezierPath(
arcCenter: CGPoint(x:halfSize,y:halfSize),
radius: CGFloat(halfSize - (4.0/2) ),
startAngle: startPoint,
endAngle: startPoint + CGFloat(Double.pi * 2 * ratio/10),
clockwise: true)
}
CAlayer.path = circularPath.cgPath
CAlayer.fillColor = UIColor.clear.cgColor
if self.percentage >= 7.0 && self.percentage <= 10.0 {
CAlayer.strokeColor = UIColor(red: 50/255, green: 205/255, blue: 50/255, alpha: draw.alphaColor).cgColor
} else if self.percentage >= 4.0 && self.percentage <= 6.9 {
CAlayer.strokeColor = UIColor(red: 255/255, green: 255/255, blue: 51/255, alpha: draw.alphaColor).cgColor
} else if self.percentage >= 0.0 && self.percentage <= 3.9 {
CAlayer.strokeColor = UIColor(red: 255/255, green: 69/255, blue: 0/255, alpha: draw.alphaColor).cgColor
}
CAlayer.lineWidth = 3.0
CAlayer.lineCap = .round
CAlayer.strokeEnd = 1.0
self.layer.addSublayer(CAlayer)
}
}
The problem is that the data is transmitted correctly, and then somehow magically they are mixed exactly in CircleChartView.
Hierarchy:
Below is the code of what data should come, for example, in the first cell. (Scrolling down/up was done several times, and the screen has a completely different filling of the circle and color):
if indexPath.row == 0 {
print("Row \(0): \(movies[0].voteAverage!)")
}
I found the problem. Moved everything to the func drawCircle()
func drawCircle() {
drawCircle(CAlayer: circleLayer, draw: .baseCircle, ratio: 0.0)
drawCircle(CAlayer: progressLayer, draw: .fillProgressCircle, ratio: percentage)
}
And I call her in UIView:
override func draw(_ rect: CGRect) {
drawCircle()
}
And in UICollectionViewCell:
override func prepareForReuse() {
super.prepareForReuse()
chartView.circleLayer.removeFromSuperlayer()
chartView.progressLayer.removeFromSuperlayer()
}
And in UIViewController:
cell.chartView.drawCircle()

Custom UILabel with gradient text colour always become black

I created a custom UILabel to show grediant text but it always show the text as black...when i but the same code on a view controller it works!!
import UIKit
class GradientLabel: UILabel {
override func awakeFromNib() {
super.awakeFromNib()
let gredient = GradientView.init(frame: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height))
gredient.bottomColor = #colorLiteral(red: 0.1764705926, green: 0.4980392158, blue: 0.7568627596, alpha: 1)
gredient.topColor = #colorLiteral(red: 0.1764705926, green: 0.01176470611, blue: 0.5607843399, alpha: 1)
setTextColorToGradient(image: imageWithView(view: gredient)!)
}
func imageWithView(view: UIView) -> UIImage? {//bet7awel uiview to image
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img
}
func setTextColorToGradient(image: UIImage) {//beta7'od image we tekteb beha el text fe el label
UIGraphicsBeginImageContext(frame.size)
image.draw(in: bounds)
let myGradient = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
textColor = UIColor(patternImage: myGradient!)
}
}
You don’t want to rely on awakeFromNib. Furthermore, you really don’t want to do this in init, either, as you want to be able to respond to size changes (e.g. if you have constraints and the frame changes after the label is first created).
Instead, update your gradient from layoutSubviews, which is called whenever the view’s frame changes:
#IBDesignable
class GradientLabel: UILabel {
#IBInspectable var topColor: UIColor = #colorLiteral(red: 0.1764705926, green: 0.4980392158, blue: 0.7568627596, alpha: 1) {
didSet { setNeedsLayout() }
}
#IBInspectable var bottomColor: UIColor = #colorLiteral(red: 0.1764705926, green: 0.01176470611, blue: 0.5607843399, alpha: 1) {
didSet { setNeedsLayout() }
}
override func layoutSubviews() {
super.layoutSubviews()
updateTextColor()
}
private func updateTextColor() {
let image = UIGraphicsImageRenderer(bounds: bounds).image { _ in
let gradient = GradientView(frame: bounds)
gradient.topColor = topColor
gradient.bottomColor = bottomColor
gradient.drawHierarchy(in: bounds, afterScreenUpdates: true)
}
textColor = UIColor(patternImage: image)
}
}
That results in:
Note,
I’ve used the new UIGraphicsImageRenderer rather than UIGraphicsBeginImageContext, UIGraphicsGetImageFromCurrentImageContext, and UIGraphicsEndImageContext.
Rather than building a CGRect manually, I just use bounds.
I’ve made it #IBDesignable so that I can use it directly in Interface Builder. I’ve also made the colors #IBInspectable so you can adjust the colors right in IB rather than going to code. Clearly, you only have to do this if you want to see the gradient effect rendered in IB.
I’ve made it update the gradient (a) when the label needs to be laid out again; and (b) whenever you change either of the colors.
For what it’s worth, this is the GradientView I used for the purposes of this example:
#IBDesignable
class GradientView: UIView {
override class var layerClass: AnyClass { return CAGradientLayer.self }
private var gradientLayer: CAGradientLayer { return layer as! CAGradientLayer }
#IBInspectable var topColor: UIColor = .white { didSet { updateColors() } }
#IBInspectable var bottomColor: UIColor = .blue { didSet { updateColors() } }
override init(frame: CGRect = .zero) {
super.init(frame: frame)
updateColors()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
updateColors()
}
private func updateColors() {
gradientLayer.colors = [topColor.cgColor, bottomColor.cgColor]
}
}
In this case, because we’re setting the layerClass to the gradient, all we need to do is to configure it during init, and the base layerClass will take care of responding the size changes.
Alternatively, you can just draw your gradient using CoreGraphics:
#IBDesignable
class GradientLabel: UILabel {
#IBInspectable var topColor: UIColor = #colorLiteral(red: 0.1764705926, green: 0.4980392158, blue: 0.7568627596, alpha: 1) {
didSet { setNeedsLayout() }
}
#IBInspectable var bottomColor: UIColor = #colorLiteral(red: 0.1764705926, green: 0.01176470611, blue: 0.5607843399, alpha: 1) {
didSet { setNeedsLayout() }
}
override func layoutSubviews() {
super.layoutSubviews()
updateTextColor()
}
private func updateTextColor() {
let image = UIGraphicsImageRenderer(bounds: bounds).image { context in
let colors = [topColor.cgColor, bottomColor.cgColor]
guard let gradient = CGGradient(colorsSpace: nil, colors: colors as CFArray, locations: nil) else { return }
context.cgContext.drawLinearGradient(gradient,
start: CGPoint(x: bounds.midX, y: bounds.minY),
end: CGPoint(x: bounds.midX, y: bounds.maxY),
options: [])
}
textColor = UIColor(patternImage: image)
}
}
This achieves the same as the first example, but is potentially a tad more efficient.
You should init your label instead of awakeFromNib cause you are not using a xib for your label
class GradientLabel: UILabel {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
and an extension would be perfect to make your code reusable
extension UILabel {
func setTextColorToGradient(_ image: UIImage) {
UIGraphicsBeginImageContext(frame.size)
image.draw(in: bounds)
let myGradient = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
textColor = UIColor(patternImage: myGradient!)
}
}
usage:
label.setTextColorToGradient(UIImage(named:"someImage")!)