UITableViewCell setHighlighted, setSelected "animateAlongsideTransition" or similar - ios

I want to be able to add custom animations to subclasses of UITableViewCell that would override methods such as:
override func setHighlighted(highlighted: Bool, animated: Bool) {
}
override func setSelected(selected: Bool, animated: Bool) {
}
and match the animation curve and animation duration for the default animations those methods perform.
In other words, how can I find the information about the current animations provided by Apple. I need this in order to add my own custom animations that perfectly matches the default ones

You can subclass your custom cell in your table view.
And here I create a simple example in Swift where I change the value of a label inside the cell:
import UIKit
class userTableViewCell: UITableViewCell {
#IBOutlet weak var userLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
self.highlighted = false
self.userLabel.alpha = 0.0
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected {
self.highlighted = true
} else {
self.highlighted = false
}
// Configure the view for the selected state
}
override var highlighted: Bool {
get {
return super.highlighted
}
set {
if newValue {
// you could put some animations here if you want
UIView.animateWithDuration(0.7, delay: 1.0, options: .CurveEaseOut, animations: {
self.userLabel.text = "select"
self.userLabel.alpha = 1.0
}, completion: { finished in
print("select")
})
}
else {
self.userLabel.text = "unselect"
}
super.highlighted = newValue
}
}
}
And with the storyboard what you must have:

Related

UISearchBar resignFirstResponder not working

UIViewController needs to hide keyboard inside viewWillDisappear or viewDidDisappear methods. UIViewController stays in memory after disappearing and can be presented again. On first appearance UISearchBar is not firstResponder and keyboard is hidden. But if I pop UIViewController with keyboard shown and then push it again - keyboard is not hidden, however I call:
override func viewDidLoad() {
super.viewDidLoad()
instrumentsTableView.register(UINib(nibName: kDealsFilterInstrumentTableViewCellNib, bundle: nil), forCellReuseIdentifier: kDealsFilterInstrumentTableViewCellReusableId)
instrumentsTableView.dataSource = self
instrumentsTableView.delegate = self
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
if presenter.numberOfInstruments != 0 {
instrumentsTableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false)
}
KeyboardManager.shared.unsubscribe()
instrumentsSearchBar.text = ""
presenter.findInstruments(with: "") //just sets settings to default/ reloads data
instrumentsSearchBar.endEditing(true)
instrumentsSearchBar.resignFirstResponder()
view.endEditing(true)
view.resignFirstResponder()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
KeyboardManager.shared.subscribe(self)
}
KeyboardManager - sends notification if keyboard's state has changed, if relevant:
final class KeyboardManager {
private init() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: Notification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: Notification.Name.UIKeyboardWillHide, object: nil)
}
static let shared = KeyboardManager()
#objc private func keyboardWillShow(_ notification: Notification) {
if let keyboardSize = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue {
let height = keyboardSize.cgRectValue.height
keyboardHeight = height
keyboardState = .shown
}
}
#objc private func keyboardWillHide(_ notification: Notification) {
keyboardHeight = 0
keyboardState = .hidden
}
private weak var subscriber: KeyboardManagerDelegate?
func subscribe(_ delegate: KeyboardManagerDelegate) {
subscriber = delegate
}
func unsubscribe() {
subscriber = nil
}
private var keyboardHeight: CGFloat = 0
private var keyboardState: KeyboardState = .hidden {
didSet {
if keyboardState != oldValue {
subscriber?.keyboardDidChange(state: keyboardState, height: keyboardHeight)
}
}
}
}
enum KeyboardState {
case shown
case hidden
}
protocol KeyboardManagerDelegate: class {
func keyboardDidChange(state: KeyboardState, height: CGFloat)
}
I've tried to use this code inside viewWillAppear and viewWillDisappear - but UISearchBar is still firstResponder. If I pop with keyboard being hidden - it stays hidden. What might be the problem?
Screencast:
Sample project with the same issue on bitbucket
For keyboard issue this will work fine,
self.view.endEditing(true)
Write this in viewWillDisappear or viewDidDisappear
I've tried your code: Sample project with the same issue on bitbucket and its working as expected and fine.
Here is that code.
class ViewController: UIViewController {
#IBAction func btnShowSearch(button: UIButton) {
if let search = self.storyboard?.instantiateViewController(withIdentifier: "SeachBarViewController") {
self.navigationController?.pushViewController(search, animated: true)
}
}
}
// SeachBarViewController
class SeachBarViewController: UIViewController {
#IBOutlet var searchBar: UISearchBar!
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
attemptToHidKeyboard()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
attemptToHidKeyboard()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
attemptToHidKeyboard()
}
override func didMove(toParentViewController parent: UIViewController?) {
if parent == nil {
attemptToHidKeyboard()
}
}
override func willMove(toParentViewController parent: UIViewController?) {
if parent == nil {
attemptToHidKeyboard()
}
}
private func attemptToHidKeyboard() {
self.searchBar.resignFirstResponder()
self.searchBar.endEditing(true)
self.view.resignFirstResponder()
self.view.endEditing(true)
}
}
Here is result:

How to detect a tap on an UIImageView while it is in the process of animation?

I try to detect a tap on an UIImageView while it is in the process of animation, but it does't work.
What I do (swift 4):
added UIImageView via StoryBoard:
#IBOutlet weak var myImageView: UIImageView!
 
doing animation:
override func viewWillAppear (_ animated: Bool) {
        super.viewWillAppear (animated)
        myImageView.center.y + = view.bounds.height
    }
    override func viewDidAppear (_ animated: Bool) {
        super.viewDidAppear (animated)
        UIView.animate (withDuration: 10, delay: 0, options: [.repeat, .autoreverse, .allowUserInteraction], animations: {
            self.myImageView.center.y - = self.view.bounds.height
        })
    }
try to detect the tap:
override func viewDidLoad () {
        super.viewDidLoad ()
        let gestureSwift2AndHigher = UITapGestureRecognizer (target: self, action: #selector (self.actionUITapGestureRecognizer))
        myImageView.isUserInteractionEnabled = true
        myImageView.addGestureRecognizer (gestureSwift2AndHigher)
    }
    #objc func actionUITapGestureRecognizer () {
        print ("actionUITapGestureRecognizer - works!")
    }
Please, before voting for a question, make sure that there are no normally formulated answers to such questions, understandable to the beginner and written in swift above version 2, so I can not apply them for my case.
Studying this problem, I realized that it is necessary to also tweak the frame !? But this is still difficult for me. Tell me, please, what I need to add or change in the code below.
Thank you for your help.
class ExampleViewController: UIViewController {
#IBOutlet weak var myImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// action by tap
let gestureSwift2AndHigher = UITapGestureRecognizer(target: self, action: #selector (self.actionUITapGestureRecognizer))
myImageView.isUserInteractionEnabled = true
myImageView.addGestureRecognizer(gestureSwift2AndHigher)
}
// action by tap
#objc func actionUITapGestureRecognizer (){
print("actionUITapGestureRecognizer - works!") // !!! IT IS DOES NOT WORK !!!
}
// hide UIImageView before appear
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
myImageView.center.y += view.bounds.height
}
// show UIImageView after appear with animation
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 10, delay: 0, options: [.repeat, .autoreverse, .allowUserInteraction], animations: {
self.myImageView.center.y -= self.view.bounds.height
})
}
}
To detect touch on a moving (animated) view, simply override hitTest using the presentation layer:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return (layer.presentation()!.frame)
.contains(self.convert(point, to: superview!)) ? self : nil
}
In the example at hand
It works with any and all gesture recognizers
DO NOT modify any frames, or anything else, at the view controller level
Simply subclass the view itself, adding the override above
Don't forget that naturally, if you want to stop the animation once the item is grabbed, do that (in your view controller) with yourPropertyAnimator?.stopAnimation(true) , yourPropertyAnimator = nil
You CANNOT do what you want using UITapGestureRecognizer because it uses frame based detection and detects if a touch was inside your view by checking against its frame..
The problem with that, is that animations already set the view's final frame before the animation even begins.. then it animates a snapshot of your view into position before showing your real view again..
Therefore, if you were to tap the final position of your animation, you'd see your tap gesture get hit even though your view doesn't seem like it's there yet.. You can see that in the following image:
https://i.imgur.com/Wl9WRfV.png
(Left-Side is view-hierarchy inspector)..(Right-Side is the simulator animating).
To solve the tapping issue, you can try some sketchy code (but works):
import UIKit
protocol AnimationTouchDelegate {
func onViewTapped(view: UIView)
}
protocol AniTouchable {
var animationTouchDelegate: AnimationTouchDelegate? {
get
set
}
}
extension UIView : AniTouchable {
private struct Internal {
static var key: String = "AniTouchable"
}
private class func getAllSubviews<T: UIView>(view: UIView) -> [T] {
return view.subviews.flatMap { subView -> [T] in
var result = getAllSubviews(view: subView) as [T]
if let view = subView as? T {
result.append(view)
}
return result
}
}
private func getAllSubviews<T: UIView>() -> [T] {
return UIView.getAllSubviews(view: self) as [T]
}
var animationTouchDelegate: AnimationTouchDelegate? {
get {
return objc_getAssociatedObject(self, &Internal.key) as? AnimationTouchDelegate
}
set {
objc_setAssociatedObject(self, &Internal.key, newValue, .OBJC_ASSOCIATION_ASSIGN)
}
}
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let touchLocation = touch.location(in: self)
var didTouch: Bool = false
let views = self.getAllSubviews() as [UIView]
for view in views {
if view.layer.presentation()?.hitTest(touchLocation) != nil {
if let delegate = view.animationTouchDelegate {
didTouch = true
delegate.onViewTapped(view: view)
}
}
}
if !didTouch {
super.touchesBegan(touches, with: event)
}
}
}
class ViewController : UIViewController, AnimationTouchDelegate {
#IBOutlet weak var myImageView: UIImageView!
deinit {
self.myImageView.animationTouchDelegate = nil
}
override func viewDidLoad() {
super.viewDidLoad()
self.myImageView.isUserInteractionEnabled = true
self.myImageView.animationTouchDelegate = self
}
func onViewTapped(view: UIView) {
print("Works!")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
myImageView.center.y += view.bounds.height
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 5, delay: 0, options: [.repeat, .autoreverse, .allowUserInteraction], animations: {
self.myImageView.center.y -= self.view.bounds.height
})
}
}
It works by overriding touchesBegan on the UIView and then checking to see if any of the touches landed inside that view.
A MUCH better approach would be to just do it in the UIViewController instead..
import UIKit
protocol AnimationTouchDelegate : class {
func onViewTapped(view: UIView)
}
extension UIView {
private class func getAllSubviews<T: UIView>(view: UIView) -> [T] {
return view.subviews.flatMap { subView -> [T] in
var result = getAllSubviews(view: subView) as [T]
if let view = subView as? T {
result.append(view)
}
return result
}
}
func getAllSubviews<T: UIView>() -> [T] {
return UIView.getAllSubviews(view: self) as [T]
}
}
class ViewController : UIViewController, AnimationTouchDelegate {
#IBOutlet weak var myImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.myImageView.isUserInteractionEnabled = true
}
func onViewTapped(view: UIView) {
print("Works!")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
myImageView.center.y += view.bounds.height
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 5, delay: 0, options: [.repeat, .autoreverse, .allowUserInteraction], animations: {
self.myImageView.center.y -= self.view.bounds.height
})
}
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let touchLocation = touch.location(in: self.view)
var didTouch: Bool = false
for view in self.view.getAllSubviews() {
if view.isUserInteractionEnabled && !view.isHidden && view.alpha > 0.0 && view.layer.presentation()?.hitTest(touchLocation) != nil {
didTouch = true
self.onViewTapped(view: view)
}
}
if !didTouch {
super.touchesBegan(touches, with: event)
}
}
}

SubView(nib) wont remove after calling removeFromSuperView()

I have an overlay view to segregate content, I'm checking for authentication in viewWillAppear() and I have a Notification subscribed to my Auth method. If I authenticate before any of my other views appear the overlay does not show up, however it does on the first view and will not go away even after calling removeFromSuperView().
import UIKit
import FirebaseAuth
class ProtectedViewController: UIViewController, ForceSignInBannerDelegate,
SignUpViewControllerDelegate, LoginViewControllerDelegate{
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
NotificationCenter.default.addObserver(self, selector: #selector(checkAuthentication), name: .myNotification, object: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
self.checkAuthentication()
}
func checkAuthentication() {
let bannerViewController = ForceSignInBanner.instanceFromNib() as! ForceSignInBanner
bannerViewController.delegate = self
if (!AuthenticationService.sharedInstance.isAuthenticated()) {
self.setView(view: bannerViewController, hidden: false)
print("Need to login")
} else if(AuthenticationService.sharedInstance.isAuthenticated()) {
self.setView(view: bannerViewController, hidden: true)
}
}
func setView(view: UIView, hidden: Bool) {
UIView.transition(with: view, duration: 0.5, options: .transitionCrossDissolve, animations: { _ in
view.isHidden = hidden
if hidden {
view.removeFromSuperview()
} else {
self.view.addSubview(view)
}
}, completion: nil)
}
It's because you're trying to remove a new ForceSignInBanner each time. Ideally you should create it once and keep a reference to the ForceSignInBanner created (as an optional property of ProtectedViewController).
Then remove the ForceSignInBanner that you've stored in the property.
class ProtectedViewController: UIViewController, ForceSignInBannerDelegate {
// This lazily loads the view when the property is first used and sets the delegate.
// Ideally you wouldn't force-case the `as` but I've left it for simplicity here.
private lazy var forceSignInBannerView: ForceSignInBanner = {
let forceSignInBannerView = ForceSignInBanner.instanceFromNib() as! ForceSignInBanner
forceSignInBannerView.delegate = self
return forceSignInBannerView
}()
// ... your other code ... //
fun toggleForceSignInBannerViewVisibility(isVisible: Bool) {
if isVisible {
view.addSubview(forceSignInBannerView)
} else {
forceSignInBannerView.removeFromSuperview()
}
}
}

prefersStatusBarHidden slide animation not working on device

I have two view controllers. MainViewController and SecondViewController (this one is embedded in a Navigation Controller).
MainViewController has a UIButton that will modally present SecondViewController, while SecondViewController has a UIButton that will dismiss itself.
Each of them have the following code:
var statusBarHidden = false {
didSet {
UIView.animate(withDuration: 0.5) { () -> Void in
self.setNeedsStatusBarAppearanceUpdate()
}
}
}
override var prefersStatusBarHidden: Bool {
return statusBarHidden
}
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
return .slide
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
statusBarHidden = true
}
The slide animation of the status bar works great in the simulator but not on the actual device, what am i doing wrong ?
I'm using xCode 8.2.1 and Swift 3
What i ended up doing was this. I created a variable that links to the view of the status bar and added functions so i can do what i need.
extension UIApplication {
var statusBarView: UIView? {
return value(forKey: "statusBar") as? UIView
}
func changeStatusBar(alpha: CGFloat) {
statusBarView?.alpha = alpha
}
func hideStatusBar() {
UIView.animate(withDuration: 0.3) {
self.statusBarView?.alpha = 0
}
}
func showStatusBar() {
UIView.animate(withDuration: 0.3) {
self.statusBarView?.alpha = 1
}
}
}
A typical use would be:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let alpha = tableView.contentOffset.y / 100
UIApplication.shared.changeStatusBar(alpha: alpha)
}

UILabel is not appearing in delayed sequence as expected

I want to make my UILabels appear in a delayed sequence. Thus one after another.
The code below works when I make them fade in using the alpha value but it doesn't do what I want it to do when I use the .hidden property of the UILabels.
The code makes my UILabels appear at all the same time instead of sum1TimeLabel first after 5 seconds, sum2TimeLabel second after 30 seconds and finally sum3TimeLabel third after 60 seconds. What am I doing wrong?
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
UIView.animateWithDuration(5.0, animations: {
self.sum1TimeLabel!.hidden = false;
})
UIView.animateWithDuration(30.0, animations: {
self.sum2TimeLabel!.hidden = false;
})
UIView.animateWithDuration(60.0, animations: {
self.sum3TimeLabel!.hidden = false;
})
}
As explained by SO user #matt in this answer, you can simply create a delay function with Grand Central Dispatch instead of using animateWithDuration, as animateWithDuration is usually meant for animating things over a period of time, and since the value you are animating is a Bool, it can't animate it.
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
delay(5.0) {
self.sum1TimeLabel?.hidden = false
}
delay(30.0) {
self.sum2TimeLabel?.hidden = false
}
delay(60.0) {
self.sum3TimeLabel?.hidden = false
}
}
func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure)
}
// Try this
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.sum1TimeLabel!.hidden = true;
self.sum2TimeLabel!.hidden = true;
self.sum3TimeLabel!.hidden = true;
UIView.animateWithDuration(5.0, animations: {
}, completion: {
self.sum1TimeLabel!.hidden = false;
})
UIView.animateWithDuration(30.0, animations: {
}, completion: {
self.sum2TimeLabel!.hidden = false;
})
UIView.animateWithDuration(60.0, animations: {
}, completion: {
self.sum3TimeLabel!.hidden = false;
})
}
class ViewController: UIViewController {
#IBOutlet weak var label1: UILabel!
#IBOutlet weak var label2: UILabel!
#IBOutlet weak var label3: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
label1.alpha = 0
label2.alpha = 0
label3.alpha = 0
UIView.animateWithDuration(5.0, animations: { () -> Void in
print("animation on label1")
self.label1.alpha = 0.2
}) { (b) -> Void in
print("complete on label1")
self.label1.alpha = 1
UIView.animateWithDuration(5.0, animations: { () -> Void in
print("animation on label2")
self.label2.alpha = 0.2
}, completion: { (b) -> Void in
print("complete on label2")
self.label2.alpha = 1
UIView.animateWithDuration(5.0, animations: { () -> Void in
print("animation on label3")
self.label3.alpha = 0.2
}, completion: { (b) -> Void in
print("complete on label3")
self.label3.alpha = 1
})
})
}
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You are NOT able to animate hidden property. there is two states only. how to change hidden continuously with time? instead you can animate alpha :-). try change the alpha value inside animation block to 0, or to 1.

Resources