Animate UIView (when Drawing on UIGraphicsGetCurrentContext) - swift

I created a class that draws a single-bar-chart that indicates a percent value, when one is set. How can I animate the bar, so that it grows from the left to the right?
The animate-function that I already have does not work like it want it to...
class EasyLineView : UIView {
var fillRect = CGRect.init(x: 0, y: 0, width: 0, height: 0)
var percentValue: CGFloat = 0 {
didSet {
self.layoutIfNeeded()
}
}
var orientation: BarOrientation = .vertical
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.black
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
print("drawing with \(rect.size.width)")
let r = self.bounds // the view's bounds
let ctx = UIGraphicsGetCurrentContext() // get the current context
ctx!.setFillColor(UIColor.yellow.cgColor) // set fill color to the given color if it's provided, else use clearColor
if (self.orientation == .vertical){
fillRect = CGRect.init(x: 0, y: 0, width: r.size.width, height: percentValue*r.size.height)
} else {
fillRect = CGRect.init(x: 0, y: 0, width: percentValue*r.size.width, height: r.size.height)
}
ctx!.fill(fillRect)
}
// does not work
func animate(){
UIView.animate(withDuration: 5, delay: 0, animations: {
self.layoutIfNeeded()
})
}
}

Don't do it like that. Drawing a box in draw(_:) will give you poor performance and no flexibility for animations.
If all you want is an animatable box whose size you can configure, add a subview and set the background colour to your drawing colour. Then you can use autolayout or manual frame setting, in an animation block, to make the box animate to the size you want.

Related

UItextfield text covers the clear button in the textfield in Swift

Hello, I'm working on a UITextField now and as the image above shows, the text covers the clear button and I want to fix that.
In this text field, I added a padding on the left side of the text and the right side of the clear button.
class testTextField: UITextField {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func commonInit() {
let leftPadding = UIView(frame: CGRect(x: 0, y: 0, width: 17, height: 0))
leftPadding.backgroundColor = .clear
self.leftView = leftPadding
self.leftViewMode = .always
// tried adding padding to the rightView, but it hide the clear button
// let rightPadding = UIView(frame: CGRect(x: 0, y: 0, width: 1, height: 0))
// rightPadding.backgroundColor = .red
// self.rightView = rightPadding
// self.rightViewMode = .always
self.backgroundColor = .white
self.borderStyle = .none
self.layer.cornerRadius = 12
self.font = UIFont.systemFont(ofSize: 16)
self.textColor = .black
self.clearButtonMode = .whileEditing
self.isUserInteractionEnabled = true
}
override func clearButtonRect(forBounds bounds: CGRect) -> CGRect {
let originalRect = super.clearButtonRect(forBounds: bounds)
return originalRect.offsetBy(dx: -22, dy: 0)
}
// also tried changing the width of the textRext, but after I implement the following code, the textfield becomes non-clickable...
// override func textRect(forBounds bounds: CGRect) -> CGRect {
// self.bounds.size.width = bounds.size.width - 20.0;
// return bounds;
// }
// override func editingRect(forBounds bounds: CGRect) -> CGRect {
// self.bounds.size.width = bounds.size.width - 20.0;
// return bounds;
// }
}
After doing some research(like this), as I put some comments in the code, I tried adding padding to rightView and changing the textRect, but none of them worked.
I want to keep the clear button where it is now, but I want some space on the left side of the clear button, something like the image below...
Thanks to #Shadowrun, I was able to solve the issue I had.
I needed to subclass the UITextField and add editingRect. Then return the CGRect with whatever numbers I want to put. My code is something like this.
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return CGRect(x: 17, y: 0, width: bounds.size.width - 70, height: bounds.size.height)
}

How to split the background color of UIView in Swift?

I want to differentiate the background color in my app screen horizontally.
I have tried this, it's going nowhere.
var halfView1 = backgroundView.frame.width/2
backgroundView.backgroundColor.halfView1 = UIColor.black
backgroundView is an outlet from View object on storyboard
For example, half of the screen is blue and another half of the screen is red.
You should create a custom UIView class and override draw rect method
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(HorizontalView(frame: self.view.bounds))
}
}
class HorizontalView: UIView {
override func draw(_ rect: CGRect) {
super.draw(rect)
let topRect = CGRect(x: 0, y: 0, width: rect.size.width/2, height: rect.size.height)
UIColor.red.set()
guard let topContext = UIGraphicsGetCurrentContext() else { return }
topContext.fill(topRect)
let bottomRect = CGRect(x: rect.size.width/2, y: 0, width: rect.size.width/2, height: rect.size.height)
UIColor.green.set()
guard let bottomContext = UIGraphicsGetCurrentContext() else { return }
bottomContext.fill(bottomRect)
}
}
It is possible if you use a custom UIView and override the draw function, here's a Playground example :
import UIKit
import PlaygroundSupport
class CustomView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.green
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
super.draw(rect)
let bottomRect = CGRect(
origin: CGPoint(x: rect.origin.x, y: rect.height / 2),
size: CGSize(width: rect.size.width, height: rect.size.height / 2)
)
UIColor.red.set()
guard let context = UIGraphicsGetCurrentContext() else { return }
context.fill(bottomRect)
}
}
let view = CustomView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
PlaygroundPage.current.liveView = view

Gradient Layer not showing on UIButton

I am trying to create a custom UIButton that i can use throughout my application. I would like to apply a gradient as its background however the gradient is not showing up using this very simple Code.
It would be very helpful if someone could point out my mistake in the code below.
class GradientBtn: UIButton {
let gradientLayer = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame: frame)
themeConfig()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
themeConfig()
}
private func themeConfig() {
//shadow
layer.shadowOffset = CGSize.zero
layer.shadowColor = UIColor.gray.cgColor
layer.shadowOpacity = 1.0
//titletext
setTitleColor(Theme.colorWhite, for: .normal)
titleLabel?.font = UIFont(name: Theme.fontAvenir, size: 18)
//rounded corners
layer.cornerRadius = frame.size.height / 2
//gradient
gradientLayer.locations = [0.0, 1.0]
gradientLayer.colors = [Theme.colorlightBlue, Theme.colorMidBlue]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
gradientLayer.frame = bounds
layer.insertSublayer(gradientLayer, at: 0)
}
}
Replace
gradientLayer.colors = [Theme.colorlightBlue, Theme.colorMidBlue]
with
gradientLayer.colors = [Theme.colorlightBlue.cgColor, Theme.colorMidBlue.cgColor]
And it's always good to add
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = self.bounds
}
Sh_Khan is right about the CGColor references, but a few additional refinements:
Rather than adding the gradient as a sublayer, make it the layerClass of the UIButton. This makes animations much better (e.g. if the button changes size on device rotation).
When you do this, remove all attempts to set the frame of the gradientLayer.
In layoutSubviews, you’ll no longer need to update the gradientLayer.frame, so remove that entirely, but you do want to put your corner rounding there.
It’s extremely unlikely to ever matter, but when you set layer.cornerRadius (or any property of the view, its layer, or that layer's sublayers), you should never refer to self.frame. The frame of the view is the CGRect in the superview’s coordinate system. Only refer to the view’s bounds within the subclass; never its frame.
Thus:
class GradientBtn: UIButton {
override class var layerClass: AnyClass { return CAGradientLayer.self }
private var gradientLayer: CAGradientLayer { return layer as! CAGradientLayer }
override init(frame: CGRect = .zero) {
super.init(frame: frame)
themeConfig()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
themeConfig()
}
private func themeConfig() {
//shadow
layer.shadowOffset = .zero
layer.shadowColor = UIColor.gray.cgColor
layer.shadowOpacity = 1
//titletext
setTitleColor(Theme.colorWhite, for: .normal)
titleLabel?.font = UIFont(name: Theme.fontAvenir, size: 18)
//gradient
gradientLayer.locations = [0, 1]
gradientLayer.colors = [Theme.colorlightBlue, Theme.colorMidBlue].map { $0.cgColor }
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 1, y: 1)
}
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = min(bounds.height, bounds.width) / 2
}
}

customize UIPageControl dots

I'm currently trying to customize the UIPageControl so it will fit my needs. However I seem to be having a problem when implementing some logic in the draw
What I want to do is to be able to user IBInspectable variables to draw out the UIPageControl however those seem to still be nil when the draw method is being called and when I try to implement my logic in the awakeFromNib for instance it won't work.
What I did so far is the following
class BoardingPager: UIPageControl {
#IBInspectable var size: CGSize!
#IBInspectable var borderColor: UIColor!
#IBInspectable var borderWidth: CGFloat! = 1
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.pageIndicatorTintColor = UIColor.clear
}
override func draw(_ rect: CGRect) {
super.draw(rect)
setDots()
}
func setDots() {
for i in (0..<self.numberOfPages) {
let dot = self.subviews[i]
if size != nil {
let dotFrame = CGRect(x: size.width/2, y: size.height/2, width: size.width, height: size.height)
dot.frame = dotFrame
}
if i != self.currentPage {
dot.layer.cornerRadius = dot.frame.size.height / 2
dot.layer.borderColor = borderColor.cgColor
dot.layer.borderWidth = borderWidth
}
}
}
}
Another problem I'm facing is that I want to remove/add a border when the current page changes.
I'm hoping someone will be able to help me out

How do you subclass UIView and add another UIView inside of it with the same frame

Hi I am trying to create a custom loading bar view by subclassing UIView. I want to create one UIView with a fixed frame, and another UIView that is inside of it. When I initialize the inner UIView, with the frame passed in by this method override init(frame: CGRect), the two views have different origins. I want the two views to be directly on top of each other to start out. I also want to be able to update the innerBar by calling this uploadBar.setLoadingPercentage(percent: 53.5)
Here is the code:
Creating the UploadBar
let uploadBar = UploadBar(frame: CGRect(x: 40, y: 40, width: 400, height: 40))
view.addSubview(uploadBar)
Subclassing UploadBar
import UIKit
class UploadBar: UIView {
var innerBar: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white
innerBar = UIView(frame: frame)
innerBar.backgroundColor = UI.customBlue()
addSubview(innerBar)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setLoadingPercentage(percent: Double) {
// change innerBar's frame and redraw
}
}
For your inner view, you only require the width and height from the parent rect. The x and y should be zero relative to the parent view:
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white
let innerRect = CGRect(x: 0, y: 0, width: frame.width, height: frame.height)
innerBar = UIView(frame: innerRect)
innerBar.backgroundColor = UI.customBlue()
addSubview(innerBar)
}