By default the highlight effect of a UITableViewCell looks like this:
I'm looking for a similar effect that instead of grey is a custom color (for sake of example the UIColor default red value). I've tried to implement this myself using the setHighlighted delegate but it doesn't produce the animated effect the default style does.
This is the code I have used and the undesired effect that is achieved:
override func setHighlighted(_ highlighted: Bool, animated: Bool) {
super.setHighlighted(highlighted, animated: animated)
if highlighted {
self.backgroundColor = .red
}
}
Alongside setting cell.selectionStyle = .none in cellForRowAt.
Is there any way to produce a native-like animated custom highlight color?
You need to add another condition "else" for your "if" condition and clear the red background color to normal.
if highlighted {
self.backgroundColor = .red
}else {
self.backgroundColor = .white
}
(Edited)
You could use gestures to simulate the behaviour of the native, something like this:
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: "longPressed:")
yourCell.view.addGestureRecognizer(longPressRecognizer)
func longPressed(sender: UILongPressGestureRecognizer)
{
if sender.state == .began {
yourCell.animate(withDuration: 0.2, delay:0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEeaseOut, animations: { yourCell.backgroundColor = .red })
}
if sender.state == .ended {
yourCell.animate(withDuration: 0.2, delay:0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEeaseOut, animations: { yourCell.backgroundColor = .white })
}
}
Related
I feel like this is a dum question, but I can't find a solution. I've got a collection view, each cell has a different background color and when the user scrolls through the collection I change the labels textColor to match the background color of the cell currently in view. The label is outside of the collection view.
The detection of which cell is in view and changing the color works and I now want to apply a transition effect to the labels textColor so that it looks nicer when the user scrolls. Only problem is that my transition animation is also interacting with the scrolling animation on the collection view which looks very weird.
Is there a way to target my transition so that it only applies to the labels text color? Or maybe I need to change the way I'm detecting which cell is in view and how that triggers the animation.
Anyway my current code:
#IBOutlet weak var balanceLabel: UILabel!
#IBOutlet weak var overviewCollection: UICollectionView!
func configureVisibleIndexPath() {
let visibleCells = overviewCollection.indexPathsForVisibleItems
visibleCells.forEach {
indexPath in
if let cell = overviewCollection.cellForItem(at: indexPath), overviewCollection.bounds.contains(cell.frame) {
print("visible row is \(indexPath.row)")
let visibleIndexPath = indexPath.row
switch visibleIndexPath {
case 0:
UIView.transition(with: view, duration: 0.5, options: .transitionCrossDissolve, animations: {
self.balanceLabel.textColor = UIColor(named: "ywGreen")
})
case 1:
UIView.transition(with: view, duration: 0.5, options: .transitionCrossDissolve, animations: {
self.balanceLabel.textColor = UIColor(named: "ywYellow")
})
case 2:
UIView.transition(with: view, duration: 0.5, options: .transitionCrossDissolve, animations: {
self.balanceLabel.textColor = UIColor(named: "ywBlue")
})
case 3:
UIView.transition(with: view, duration: 0.5, options: .transitionCrossDissolve, animations: {
self.balanceLabel.textColor = UIColor(named: "ywWhite")
})
default:
UIView.transition(with: view, duration: 0.5, options: .transitionCrossDissolve, animations: {
self.balanceLabel.textColor = UIColor(named: "ywGreen")
})
}
}
}
}
Any help appreciated!
Change the with: parameter from view to self.balanceLabel.
Instead of this:
UIView.transition(with: view, duration: 0.5, options: .transitionCrossDissolve, animations: {
self.balanceLabel.textColor = UIColor(named: "ywGreen")
})
do this:
UIView.transition(with: self.balanceLabel, duration: 0.5, options: .transitionCrossDissolve, animations: {
self.balanceLabel.textColor = UIColor(named: "ywGreen")
})
I am using custom control to trigger the presentation of a UIColorPickerViewController. It triggers the color picker, but not as expected.
I was using UIColorWell previously, which does present the picker correctly (but it is an opaque implementation so I don't know how it does so). I don't want to use UIColorWell, because the shape and appearance aren't right for where I'm invoking the picker from.
Everything works exactly as desired except I can't get the color picker to present at the bottom of the screen like UIColorWell would do it. Instead, whatever I've tried to present (or show), UIColorPickerController appears near the top of the screen.
Note: Currently, to present the color picker controller, I'm using the latest iOS 15 trick with sheet controller / detents to try to anchor the picker to the bottom of the screen, but it's not working as expected (it's the technique is from the Apple Docs example, and as I've seen documented online).
What might be happening? What can I do to get the picker at the bottom of the screen, so that it doesn't obstruct my interface?
The Extension:
extension UIView {
func findViewController() -> UIViewController? {
if let nextResponder = self.next as? UIViewController {
return nextResponder
} else if let nextResponder = self.next as? UIView {
return nextResponder.findViewController()
} else {
return nil
}
}
}
Reproducible example (almost. It needs to be implemented/invoked from whatever VC [not shown here]):
This is custom control that that creates a rectangular color picker view (I'm using it to replace UIColorWell). It attempts to locate the VC which contains the view of which this custom control is a subview.
import UIKit
import QuartzCore
class RectangularColorWell : UIControl {
var colorPickerController = UIColorPickerViewController()
var selectedColor : UIColor = UIColor.clear
var lineWidth : CGFloat = 4.0 {
didSet {
self.setNeedsDisplay()
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(frame: CGRect) {
super.init(frame: frame)
colorPickerController.supportsAlpha = false
colorPickerController.delegate = self
selectedColor = backgroundColor ?? UIColor.clear
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(CustomToggleControl.controlTapped(_:)))
tapGestureRecognizer.numberOfTapsRequired = 1;
tapGestureRecognizer.numberOfTouchesRequired = 1;
self.addGestureRecognizer(tapGestureRecognizer)
self.setNeedsDisplay()
}
override func draw(_ rect: CGRect) {
guard let ctx = UIGraphicsGetCurrentContext() else { return }
ctx.setLineWidth(lineWidth)
ctx.setFillColor(backgroundColor!.cgColor)
ctx.fill(rect)
drawGradientBorder(rect, context: ctx)
}
func drawGradientBorder(_ rect: CGRect, context: CGContext) {
context.saveGState()
context.setLineWidth(lineWidth)
let path = UIBezierPath(rect: rect)
context.addPath(path.cgPath)
context.replacePathWithStrokedPath()
context.clip()
let rainbowColors : [UIColor] = [ .red, .orange, .yellow, .green, .blue, .purple ]
let colorDistribution : [CGFloat] = [ 0.0, 0.2, 0.4, 0.6, 0.8, 1.0 ]
let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: rainbowColors.map { $0.cgColor } as CFArray, locations: colorDistribution)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0, y: 0), end: CGPoint(x: rect.width, y: rect.height), options: [])
context.restoreGState()
}
#objc func controlTapped(_ gestureRecognizer :UIGestureRecognizer) {
UIView.animate(withDuration: 0.2, delay: 0.0, options: UIView.AnimationOptions.curveEaseIn, animations: {
self.alpha = 0.5
}, completion: { (finished: Bool) -> Void in
UIView.animate(withDuration: 0.2, delay: 0.0, options: UIView.AnimationOptions.curveEaseOut, animations: {
self.alpha = 1.0
}, completion: { (finished: Bool) -> Void in
})
})
setNeedsDisplay()
let vc = self.findViewController()
if let sheet = vc?.sheetPresentationController {
sheet.detents = [.medium()]
sheet.largestUndimmedDetentIdentifier = .medium
sheet.prefersScrollingExpandsWhenScrolledToEdge = false
sheet.prefersEdgeAttachedInCompactHeight = true
sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true
}
vc?.present(colorPickerController, animated: true, completion: nil)
sendActions(for: .touchDown)
}
}
extension RectangularColorWell : UIColorPickerViewControllerDelegate {
func colorPickerViewControllerDidFinish(_ controller : UIColorPickerViewController) {
self.selectedColor = controller.selectedColor
}
func colorPickerViewController(_ controller : UIColorPickerViewController, didSelect color: UIColor, continuously: Bool) {
self.backgroundColor = color
self.setNeedsDisplay()
}
}
#JordanH's final comment caused me to read up on the show/present methods on UIViewController docs. The default presentation style was what I'd been using (e.g. I hadn't set any of the presentation options on the color picker's view controller when I filed the question.
Surprisingly after reading that I could control the style, setting the picker's modalPresentationStyle property to '.pageSheet' which is the one that looks like it would work, to do a partial covering, did not work!.
The solution turned out to be adding colorPickerController.modalPresentationStyle = .popover before calling present()in the custom RectangularColorWellView). It now behaves as if it was presented from a UIColorWell control view.
hello I currently have an app that flashes a random color when the button is pressed but it gets stuck in a loop and I can't change to other colors when I press the button again. I would also like to have the message disappear when the button is pressed. thank you so much !!!
import UIKit
class ViewController: UIViewController {
let colors: [UIColor] = [
.systemYellow,
.systemGreen,
.systemPurple,
.systemPink,
.systemRed,
.systemBlue,
.systemOrange,
.black,.gray
]
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .clear
}
#IBAction func didTapButton() {
UIView.animate(withDuration: 1/25, delay: 0.0, options:[UIView.AnimationOptions.repeat, UIView.AnimationOptions.autoreverse], animations: {
self.view.backgroundColor = self.colors.randomElement()
self.view.backgroundColor = .black
self.view.backgroundColor = self.colors.randomElement()
}, completion: nil)
}
}
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
UIView.animate(withDuration: 1/25, delay: 0.0, options:[UIView.AnimationOptions.repeat, UIView.AnimationOptions.autoreverse], animations: {
self.view.backgroundColor = UIColor.black
self.view.backgroundColor = UIColor.green
self.view.backgroundColor = UIColor.darkGray
self.view.backgroundColor = UIColor.red
self.view.backgroundColor = UIColor.white }, completion: nil) }
}
Try this:
#IBAction func didTapButton()
{
self.view.backgroundColor = UIColor.clear // <-- reset initial background outside animation block
// then start the animation
UIView.animate(withDuration: 1/25, delay: 0.0, options:[.autoreverse, .repeat, .allowUserInteraction], animations: { [weak self] in
self?.view.backgroundColor = self?.colors.randomElement()
}, completion: nil)
}
i.e. just set the backgroundColor to a random color once in the animation block.
Use autoreverse to return the color to the original (clear or transparent in this example).
Use the repeat option to repeat the animation, forever.
Use the allowUserInteraction to allow user interaction during the animation.
"I would also like to have the message disappear when the button is pressed" ... not sure what this is referring to.
I'm building a calculator app, when I tap on the buttons there is a short animation. The problem is the buttons do not respond while they are animating, which makes the app feel laggy.
I found some solutions for Objective-C using:
UIViewAnimationOptionAllowUserInteraction
but nothing for Swift 3, my code looks like this:
func myButton () {
sender.layer.backgroundColor = firstColor
sender.setTitleColor(UIColor.white, for: UIControlState.normal)
UIView.animate(withDuration: 0.5) {
sender.layer.backgroundColor = secondColor
}
}
Call different method of UIView:
func myButton () {
sender.layer.backgroundColor = firstColor
sender.setTitleColor(UIColor.white, for: UIControlState.normal)
UIView.animate(withDuration: 0.5,
delay: 0.0,
options: .allowUserInteraction,
animations: {
sender.layer.backgroundColor = secondColor },
completion: nil)
}
I want the background color of my iOS app to change between four colors over x amount of seconds
This is what I have so far (it does exactly what I want when I specify just 2 colors)
I also need the animation to run in a loop infinitely.
ViewController.swift
UIView.animateWithDuration(X.0, animations: {
// Color 1
self.view.backgroundColor = UIColor(rgba)
// Color 2
self.view.backgroundColor = UIColor(rgba)
// Color 3
self.view.backgroundColor = UIColor(rgba)
// Color 4
self.view.backgroundColor = UIColor(rgba)
})
Try this out:
UIView.animateWithDuration(1.0, animations: { () -> Void in
self.view.backgroundColor = UIColor.blackColor()
}) { (Bool) -> Void in
UIView.animateWithDuration(1.0, animations: { () -> Void in
self.view.backgroundColor = UIColor.greenColor()
}, completion: { (Bool) -> Void in
UIView.animateWithDuration(1.0, animations: { () -> Void in
self.view.backgroundColor = UIColor.grayColor()
}, completion: { (Bool) -> Void in
UIView.animateWithDuration(1.0, animations: { () -> Void in
self.view.backgroundColor = UIColor.redColor()
}, completion:nil)
})
})
}
In case you want a continuous repeating animation, try this out:
UIView.animate(withDuration: 2, delay: 0.0, options:[UIView.AnimationOptions.repeat, UIView.AnimationOptions.autoreverse], animations: {
self.view.backgroundColor = UIColor.black
self.view.backgroundColor = UIColor.green
self.view.backgroundColor = UIColor.darkGray
self.view.backgroundColor = UIColor.red
}, completion: nil)
Below code helps to keep enable user interaction with animated view. A random color generation (use in case its needed).
UIView.animateWithDuration(7, delay: 1, options:
[UIViewAnimationOptions.AllowUserInteraction,
UIViewAnimationOptions.Repeat,
UIViewAnimationOptions.Autoreverse],
animations: {
self.yourView.backgroundColor = self.randomColor()
self.yourView.backgroundColor = self.randomColor()
}, completion:nil )
func randomColor() -> UIColor {
let randomRed:CGFloat = CGFloat(drand48())
let randomGreen:CGFloat = CGFloat(drand48())
let randomBlue:CGFloat = CGFloat(drand48())
return UIColor(red: randomRed, green: randomGreen, blue: randomBlue,
alpha: 1.0)
}
You have to use NSTimer:
let timer = NSTimer.scheduledTimerWithTimeInterval(5, target: self, selector: "update", userInfo: nil, repeats: true)
func update() {
let nextCollor = getNextColor()
UIView.animateWithDuration(X.0, animations: {
self.view.backgroundColor = nextCollor
})
}
func getNextColor() -> UIColor {
let currentColor = self.view.backgroundColor
if currentColor == smaple1 {
return UIColor.redColor()
} else if currentColor == smaple2 {
return UIColor.grayColor()
} else {
return UIColor.whiteColor()
}
}
NSTimer.scheduledTimerWithTimeInterval runs your code every 5 seconds
PS: do not forget invalidate timer when you done with it. Just call timer.invalidate() for it. Otherwise you get a crash.
Swift 5
override func viewWillAppear(_ animated: Bool) {
self.customButton.backgroundColor = .red
UIView.animateKeyframes(withDuration: 0.5, delay: 0, options: [.repeat, .autoreverse], animations: {
self.customButton.backgroundColor = .none
}, completion: nil)
}
i had a dynamic / unknown amount of colors to cycle through, so i refactored the Swift 5 answer a bit:
Swift 5
UIView.animate(withDuration: 1.0, delay: 0.0, options: [.repeat, .autoreverse]) {
// colors is the dynamic [UIColor] array previously defined
for color in colors {
self.colorView.backgroundColor = color
}
} completion: { (finished) in
}
this will nicely cycle through the colors with a fade-style animation -- and with the .autoreverse option, it won't "snap" to the starting color.