UIButton not showing highlight on tap in iOS7 - Swift - ios

From this post they said that it is kind of a bug in ios 7 and 8 - Button in UITableViewCell does not change to highlighted when tapped. Here I post one answer for this in Objective-C:
Create a custom UITableView subclass and custom UITableViewCell subclass.
Use this sample UITableView's initWithFrame:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
// iterate over all the UITableView's subviews
for (id view in self.subviews)
{
// looking for a UITableViewWrapperView
if ([NSStringFromClass([view class]) isEqualToString:#"UITableViewWrapperView"])
{
// this test is necessary for safety and because a "UITableViewWrapperView" is NOT a UIScrollView in iOS7
if([view isKindOfClass:[UIScrollView class]])
{
// turn OFF delaysContentTouches in the hidden subview
UIScrollView *scroll = (UIScrollView *) view;
scroll.delaysContentTouches = NO;
}
break;
}
}
}
return self;
}
Use this sample UITableViewCell's initWithStyle:reuseIdentifier:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self)
{
// iterate over all the UITableViewCell's subviews
for (id view in self.subviews)
{
// looking for a UITableViewCellScrollView
if ([NSStringFromClass([view class]) isEqualToString:#"UITableViewCellScrollView"])
{
// this test is here for safety only, also there is no UITableViewCellScrollView in iOS8
if([view isKindOfClass:[UIScrollView class]])
{
// turn OFF delaysContentTouches in the hidden subview
UIScrollView *scroll = (UIScrollView *) view;
scroll.delaysContentTouches = NO;
}
break;
}
}
}
return self;
}
However I cannot write in Swift. There are some problem:
1) I cannot do
self = super.init(style: style, reuseIdentifier: reuseIdentifier)
Error: Cannot assign to 'self' in a method
2) In Swift, I cannot do
view.class
as in Objective C:
[view class]
I have searched for hours but still cannot get what I want.
Please anyone can answer this in Swift?

In Swift, to call the superclass's designated initialiser, instead of calling self = [super initWithStyle:style reuseIdentifier:reuseIdentifier], you just use super.init(style: style, reuseIdentifier: reuseIdentifier)

Thanks to Schemetrical, this is the working version for me. (iOS 7 + 8)
First I wrote a utility function:
class func classNameAsString(obj: AnyObject) -> String {
return _stdlib_getDemangledTypeName(obj).componentsSeparatedByString(".").last!
}
then I subclass UITableView and implement this:
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
for view in self.subviews {
if (Utility.classNameAsString(view) == "UITableViewWrapperView") {
if view.isKindOfClass(UIScrollView) {
var scroll = (view as UIScrollView)
scroll.delaysContentTouches = false
}
break
}
}
}
I also subclass UITableViewCell and implement this:
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
for view in self.subviews {
if (Utility.classNameAsString(view) == "UITableViewCellScrollView") {
if view.isKindOfClass(UIScrollView) {
var scroll = (view as UIScrollView)
scroll.delaysContentTouches = false
}
}
}
}

Related

Detect that a PDFView did scroll

Does PDFKit on iOS expose a PDFView's underlying UIScrollView or is there any other way to directly detect that the user has scrolled a PDFView?
My use case is to hide a nav bar when the document is scrolled so as a workaround I've added my own pan gesture recogniser to the PDFView's parent and I do the hiding in gestureRecognizerShouldBegin and always return false but I expect there's something more like UIScrollViewDelegate that I'm missing in the docs.
Try this,
NotificationCenter.default.addObserver(self, selector: #selector(handlePageChange(notification:)), name: Notification.Name.PDFViewPageChanged, object: nil)
#objc private func handlePageChange(notification: Notification)
{
print("Page changed")
}
Does PDFKit on iOS expose a PDFView's underlying UIScrollView
No, but hopefully Apple will add this in the future. I remember that UIWebView didn't have it originally and it was added later.
or is there any other way to directly detect that the user has scrolled a PDFView
No, it looks like none of the notifications provided by PDFViewDelegate address this.
I'm migrating from UIWebView to PDFView and am using scrollViewDidScroll for a bunch of stuff, so I didn't want to rely on just adding a pan gesture recognizer. Building from #Matthijs's answer, I'm finding the UIScrollView inside the PDFView, making my class its delegate, then passing any events back to the scroll view (which was its own delegate before my class became the delegate) so it can respond to them, too. With UIWebView, this last step was not necessary, but with PDFView, zooming and possibly other functions won't work without it.
I'm overriding all the documented delegate methods to reduce the chance that this will break if Apple changes the internal function of PDFView. However, I had to check respondsToSelector in each method, because the original scroll view delegate doesn't currently implement all of them.
- (void)viewDidLoad {
// create the PDFView and find its inner scrollView
self.pdfView = [[PDFView alloc] init];
for (UIView *subview in self.pdfView.subviews) {
if ([subview isKindOfClass:[UIScrollView class]]) {
self.scrollView = (UIScrollView *)subview;
} else {
for (UIView *subsubview in subview.subviews) {
if ([subsubview isKindOfClass:[UIScrollView class]]) {
self.scrollView = (UIScrollView *)subsubview;
}
}
}
}
}
- (void)loadPDFDocument:(NSString *)URL {
// load a document, then become the delegate for the scrollView (we have to do that after loading the document)
PDFDocument *document = [[PDFDocument alloc] initWithURL:URL];
self.pdfView.document = document;
self.scrollView.delegate = self;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// *** respond to scroll events here ***
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewDidScroll:)]) {
[scrollViewDelegate scrollViewDidScroll:scrollView];
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewWillBeginDragging:)]) {
[scrollViewDelegate scrollViewWillBeginDragging:scrollView];
}
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) {
[scrollViewDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewDidEndDragging:willDecelerate:)]) {
[scrollViewDelegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate];
}
}
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewShouldScrollToTop:)]) {
return [scrollViewDelegate scrollViewShouldScrollToTop:scrollView];
}
return TRUE;
}
- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewDidScrollToTop:)]) {
[scrollViewDelegate scrollViewDidScrollToTop:scrollView];
}
}
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewWillBeginDecelerating:)]) {
[scrollViewDelegate scrollViewWillBeginDecelerating:scrollView];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewDidEndDecelerating:)]) {
[scrollViewDelegate scrollViewDidEndDecelerating:scrollView];
}
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(viewForZoomingInScrollView:)]) {
return [scrollViewDelegate viewForZoomingInScrollView:scrollView];
}
return nil;
}
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewWillBeginZooming:withView:)]) {
[scrollViewDelegate scrollViewWillBeginZooming:scrollView withView:view];
}
}
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewDidEndZooming:withView:atScale:)]) {
[scrollViewDelegate scrollViewDidEndZooming:scrollView withView:view atScale:scale];
}
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewDidZoom:)]) {
[scrollViewDelegate scrollViewDidZoom:scrollView];
}
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewDidEndScrollingAnimation:)]) {
[scrollViewDelegate scrollViewDidEndScrollingAnimation:scrollView];
}
}
- (void)scrollViewDidChangeAdjustedContentInset:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:#selector(scrollViewDidChangeAdjustedContentInset:)]) {
[scrollViewDelegate scrollViewDidChangeAdjustedContentInset:scrollView];
}
}
I did this to detect zooming and panning on a pdfView to copy those gestures to a second pdfView, and it's working perfectly fine here.
Got some help to detect vertical and horizontal panning by the PanDirectionGestureRecognizer I found here: stackoverflow.com/a/55635482/558112
class Document: UIViewController, UIScrollViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Subscribe to notifications.
NotificationCenter.default.addObserver(self, selector: #selector(onPageZoomAndPan), name: .PDFViewScaleChanged, object: pdfView
// get the scrollView in pdfView and attach gesture recognizers
outerLoop: for subView in pdfView.subviews {
for view in subView.subviews {
if let scrollView = view as? UIScrollView {
let xScrollViewPanGesture = PanDirectionGestureRecognizer(direction: .horizontal, target: self, action: #selector(onPageZoomAndPan))
xScrollViewPanGesture.delegate = self
scrollView.addGestureRecognizer(xScrollViewPanGesture)
let yScrollViewPanGesture = PanDirectionGestureRecognizer(direction: .vertical, target: self, action: #selector(onPageZoomAndPan))
yScrollViewPanGesture.delegate = self
scrollView.addGestureRecognizer(yScrollViewPanGesture)
break outerLoop
}
}
}
}
// MARK: - UIScrollViewDelegate
#objc private func onPageZoomAndPan() {
let rect = pdfView.convert(pdfView.bounds, to: pdfView.currentPage!)
pdfViewSecondScreen.scaleFactor = pdfView.scaleFactor
pdfViewSecondScreen.go(to: rect, on: pdfView.currentPage!)
}
}
enum PanDirection {
case vertical
case horizontal
}
// UIKit.UIGestureRecognizerSubclass
import UIKit.UIGestureRecognizerSubclass
class PanDirectionGestureRecognizer: UIPanGestureRecognizer {
let direction : PanDirection
init(direction: PanDirection, target: AnyObject, action: Selector) {
self.direction = direction
super.init(target: target, action: action)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)
if state == .began {
let vel = velocity(in: self.view!)
switch direction {
case .horizontal where abs(vel.y) > abs(vel.x):
state = .cancelled
case .vertical where abs(vel.x) > abs(vel.y):
state = .cancelled
default:
break
}
}
}
}
Decided on a solution other's haven't done yet. Went with key value observing on the contentOffset property of the underlying UIScrollView.
You can use this extension to run a callback every time the scroll offset changes.
var observation = pdfView.onScrollOffsetChanged { scroll in
print("PDFView scrolled to \(scroll.contentOffset).")
}
The extension
extension PDFView {
func onScrollOffsetChange(handler: #escaping (UIScrollView) -> Void) -> NSKeyValueObservation? {
detectScrollView()?.observe(\.contentOffset) { scroll, _ in
handler(scroll)
}
}
private func detectScrollView() -> UIScrollView? {
for view in subviews {
if let scroll = view as? UIScrollView {
return scroll
} else {
for subview in view.subviews {
if let scroll = subview as? UIScrollView {
return scroll
}
}
}
}
print("Unable to find a scrollView subview on a PDFView.")
return nil
}
}
try this!
(pdfView.subviews[0] as? UIScrollView)?.delegate = self
and observe the scrollview delegate
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y > 0 {
/// ...
} else {
/// ...
}
}

How to disable delaysContentTouches in UITableViewController? [duplicate]

I've looked at a ton of posts on similar things, but none of them quite match or fix this issue. Since iOS 7, whenever I add a UIButton to a UITableViewCell or even to the footerview it works "fine", meaning it receives the target action, but it doesn't show the little highlight that normally happens as you tap a UIButton. It makes the UI look funky not showing the button react to touch.
I'm pretty sure this counts as a bug in iOS7, but has anyone found a solution or could help me find one :)
Edit:
I forgot to mention that it will highlight if I long hold on the button, but not a quick tap like it does if just added to a standard view.
Code:
Creating the button:
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.titleLabel.font = [UIFont systemFontOfSize:14];
button.titleLabel.textColor = [UIColor blueColor];
[button setTitle:#"Testing" forState:UIControlStateNormal];
[button addTarget:self action:#selector(buttonPressed:) forControlEvents: UIControlEventTouchDown];
button.frame = CGRectMake(0, 0, self.view.frame.size.width/2, 40);
Things I've Tested:
//Removing gesture recognizers on UITableView in case they were getting in the way.
for (UIGestureRecognizer *recognizer in self.tableView.gestureRecognizers) {
recognizer.enabled = NO;
}
//Removing gestures from the Cell
for (UIGestureRecognizer *recognizer in self.contentView.gestureRecognizers) {
recognizer.enabled = NO;
}
//This shows the little light touch, but this isn't the desired look
button.showsTouchWhenHighlighted = YES;
In that tableview you just add this property.
tableview.delaysContentTouches = NO;
And add in cellForRowAtIndexPath after you initiate the cell you just add below code. The structure of the cell is apparently different in iOS 6 and iOS 7.
iOS 7 we have one control UITableViewCellScrollView In between UITableViewCell and content View.
for (id obj in cell.subviews)
{
if ([NSStringFromClass([obj class]) isEqualToString:#"UITableViewCellScrollView"])
{
UIScrollView *scroll = (UIScrollView *) obj;
scroll.delaysContentTouches = NO;
break;
}
}
Since iOS 8 we need to apply the same technique to UITableView subviews (table contains a hidden UITableViewWrapperView scroll view). There is no need iterate UITableViewCell subviews anymore.
for (UIView *currentView in tableView.subviews) {
if ([currentView isKindOfClass:[UIScrollView class]]) {
((UIScrollView *)currentView).delaysContentTouches = NO;
break;
}
}
This answer should be linked with this question.
I tried to add this to the accepted answer but it never went through. This is a much safer way of turning off the cells delaysContentTouches property as it does not look for a specific class, but rather anything that responds to the selector.
In Cell:
for (id obj in self.subviews) {
if ([obj respondsToSelector:#selector(setDelaysContentTouches:)]) {
[obj setDelaysContentTouches:NO];
}
}
In TableView:
self.tableView.delaysContentTouches = NO;
For a solution that works in both iOS7 and iOS8, create a custom UITableView subclass and custom UITableViewCell subclass.
Use this sample UITableView's initWithFrame:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
// iterate over all the UITableView's subviews
for (id view in self.subviews)
{
// looking for a UITableViewWrapperView
if ([NSStringFromClass([view class]) isEqualToString:#"UITableViewWrapperView"])
{
// this test is necessary for safety and because a "UITableViewWrapperView" is NOT a UIScrollView in iOS7
if([view isKindOfClass:[UIScrollView class]])
{
// turn OFF delaysContentTouches in the hidden subview
UIScrollView *scroll = (UIScrollView *) view;
scroll.delaysContentTouches = NO;
}
break;
}
}
}
return self;
}
Use this sample UITableViewCell's initWithStyle:reuseIdentifier:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self)
{
// iterate over all the UITableViewCell's subviews
for (id view in self.subviews)
{
// looking for a UITableViewCellScrollView
if ([NSStringFromClass([view class]) isEqualToString:#"UITableViewCellScrollView"])
{
// this test is here for safety only, also there is no UITableViewCellScrollView in iOS8
if([view isKindOfClass:[UIScrollView class]])
{
// turn OFF delaysContentTouches in the hidden subview
UIScrollView *scroll = (UIScrollView *) view;
scroll.delaysContentTouches = NO;
}
break;
}
}
}
return self;
}
What I did to solve the problem was a category of UIButton using the following code :
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
[NSOperationQueue.mainQueue addOperationWithBlock:^{ self.highlighted = YES; }];
}
- (void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
[self performSelector:#selector(setDefault) withObject:nil afterDelay:0.1];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[self performSelector:#selector(setDefault) withObject:nil afterDelay:0.1];
}
- (void)setDefault
{
[NSOperationQueue.mainQueue addOperationWithBlock:^{ self.highlighted = NO; }];
}
the button reacts correctly when I press on it in a UITableViewCell, and my UITableView behaves normally as the delaysContentTouches isn't forced.
Here's Roman B's answer in Swift 2:
for view in tableView.subviews {
if view is UIScrollView {
(view as? UIScrollView)!.delaysContentTouches = false
break
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
for (id view in self.tableView.subviews)
{
// looking for a UITableViewWrapperView
if ([NSStringFromClass([view class]) isEqualToString:#"UITableViewWrapperView"])
{
// this test is necessary for safety and because a "UITableViewWrapperView" is NOT a UIScrollView in iOS7
if([view isKindOfClass:[UIScrollView class]])
{
// turn OFF delaysContentTouches in the hidden subview
UIScrollView *scroll = (UIScrollView *) view;
scroll.delaysContentTouches = NO;
}
break;
}
}
}
I was having similar issues with a text-only UIButton in a UITableViewCell not highlighting upon touch. What fixed it for me was changing the buttonType from Custom back to System.
Setting delaysContentTouches to NO did the trick for the image-only UIButton in the same UITableViewCell.
self.tableView.delaysContentTouches = NO;
This is a Swift version of Raphaƫl Pinto's answer above. Don't forget to upvote him too :)
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
super.touchesBegan(touches, withEvent: event)
NSOperationQueue.mainQueue().addOperationWithBlock { () -> Void in self.highlighted = true }
}
override func touchesCancelled(touches: NSSet!, withEvent event: UIEvent!) {
super.touchesCancelled(touches, withEvent: event)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC)))
dispatch_after(time, dispatch_get_main_queue()) {
self.setDefault()
}
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
super.touchesEnded(touches, withEvent: event)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC)))
dispatch_after(time, dispatch_get_main_queue()) {
self.setDefault()
}
}
func setDefault() {
NSOperationQueue.mainQueue().addOperationWithBlock { () -> Void in self.highlighted = false }
}
Solution in Swift, iOS8 only (needs the extra work on each of the cells for iOS7):
//
// NoDelayTableView.swift
// DivineBiblePhone
//
// Created by Chris Hulbert on 30/03/2015.
// Copyright (c) 2015 Chris Hulbert. All rights reserved.
//
// This solves the delayed-tap issue on buttons on cells.
import UIKit
class NoDelayTableView: UITableView {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
delaysContentTouches = false
// This solves the iOS8 delayed-tap issue.
// http://stackoverflow.com/questions/19256996/uibutton-not-showing-highlight-on-tap-in-ios7
for view in subviews {
if let scroll = view as? UIScrollView {
scroll.delaysContentTouches = false
}
}
}
override func touchesShouldCancelInContentView(view: UIView!) -> Bool {
// So that if you tap and drag, it cancels the tap.
return true
}
}
To use, all you have to do is change the class to NoDelayTableView in your storyboard.
I can confirm that in iOS8, buttons placed inside a contentView in a cell now highlight instantly.
Slightly modified version of Chris Harrison's answer. Swift 2.3:
class HighlightButton: UIButton {
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesBegan(touches, withEvent: event)
NSOperationQueue.mainQueue().addOperationWithBlock { _ in self.highlighted = true }
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
super.touchesCancelled(touches, withEvent: event)
setDefault()
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesEnded(touches, withEvent: event)
setDefault()
}
private func setDefault() {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
NSOperationQueue.mainQueue().addOperationWithBlock { _ in self.highlighted = false }
}
}
}
The accepted answer did not work at some "taps" for me .
Finally I add the bellow code in a uibutton category(/subclass),and it works a hundred percent.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
self.backgroundColor = [UIColor greenColor];
[UIView animateWithDuration:0.05 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
self.backgroundColor = [UIColor clearColor];
} completion:^(BOOL finished)
{
}];
[super touchesBegan:touches withEvent:event];
}
I wrote a category extension on UITableViewCell to make this issue simple to address. It does basically the same thing as the accepted answer except I walk up the view hierarchy (as opposed to down) from the UITableViewCell contentView.
I considered a fully "automagic" solution that would make all cells added to a UITableView set their delaysContentTouches state to match the owning UITableView's delaysContentTouches state. To make this work I'd have to either swizzle UITableView, or require the developer to use a UITableView subclass. Not wanting to require either I settled on this solution which I feel is simpler and more flexible.
Category extension and sample harness here:
https://github.com/TomSwift/UITableViewCell-TS_delaysContentTouches
It's dead-simple to use:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// using static cells from storyboard...
UITableViewCell* cell = [super tableView: tableView cellForRowAtIndexPath: indexPath];
cell.ts_delaysContentTouches = NO;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
Here's the code for the category:
#interface UITableViewCell (TS_delaysContentTouches)
#property (nonatomic, assign) BOOL ts_delaysContentTouches;
#end
#implementation UITableViewCell (TS_delaysContentTouches)
- (UIScrollView*) ts_scrollView
{
id sv = self.contentView.superview;
while ( ![sv isKindOfClass: [UIScrollView class]] && sv != self )
{
sv = [sv superview];
}
return sv == self ? nil : sv;
}
- (void) setTs_delaysContentTouches:(BOOL)delaysContentTouches
{
[self willChangeValueForKey: #"ts_delaysContentTouches"];
[[self ts_scrollView] setDelaysContentTouches: delaysContentTouches];
[self didChangeValueForKey: #"ts_delaysContentTouches"];
}
- (BOOL) ts_delaysContentTouches
{
return [[self ts_scrollView] delaysContentTouches];
}
#end
Since objc is dynamic, and scrollView is the only class that responds to delaysContentTouches, this should work for both ios 7 and 8 (put it somewhere early in your tableViewController, like awakeFromNib):
for (id view in self.tableView.subviews)
{
if ([view respondsToSelector:#selector(delaysContentTouches)]) {
UIScrollView *scrollView = (UIScrollView *)view;
scrollView.delaysContentTouches = NO;
break;
}
}
You may also have to turn off "delaysContentTouches" in your storyboard or nib by selecting the table inside your viewController. BTW, this might not work on ios 7 if you're using a tableView inside a viewController, at least I couldn't get it to work.
That solution for me doesn't work, I fixed subclassing TableView and implementing these two methods
- (instancetype)initWithCoder:(NSCoder *)coder{
self = [super initWithCoder:coder];
if (self) {
for (id obj in self.subviews) {
if ([obj respondsToSelector:#selector(setDelaysContentTouches:)]){
[obj performSelector:#selector(setDelaysContentTouches:) withObject:NO];
}
}
}
return self;
}
- (BOOL)delaysContentTouches{
return NO;
}
Solution in Swift for iOS 7 and 8:
First I wrote a utility function:
class func classNameAsString(obj: AnyObject) -> String {
return _stdlib_getDemangledTypeName(obj).componentsSeparatedByString(".").last!
}
then I subclass UITableView and implement this:
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
for view in self.subviews {
if (Utility.classNameAsString(view) == "UITableViewWrapperView") {
if view.isKindOfClass(UIScrollView) {
var scroll = (view as UIScrollView)
scroll.delaysContentTouches = false
}
break
}
}
}
I also subclass UITableViewCell and implement this:
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
for view in self.subviews {
if (Utility.classNameAsString(view) == "UITableViewCellScrollView") {
if view.isKindOfClass(UIScrollView) {
var scroll = (view as UIScrollView)
scroll.delaysContentTouches = false
}
}
}
}
In my case the init(coder:) will run. Please put debug point in your init functions to know which init function will run, then using the code above to make it work.
Hope to help someone.
In Swift 3 this UIView extension can be used on the UITableViewCell. Preferably in the cellForRowAt method.
func removeTouchDelayForSubviews() {
for subview in subviews {
if let scrollView = subview as? UIScrollView {
scrollView.delaysContentTouches = false
} else {
subview.removeTouchDelayForSubviews()
}
}
}

Wrapping cells in UICollectionview iOS 8

I have the same problem with this question. I already tried the solution but it is not called. Where should I implement or call the method or subclass of UICollectionViewFlowLayout.
Where should I use this?
Thanks in advance.
U can do like below, this method is called automatically,
swift version
first create a new class which is the subclass of UICollectionViewFlowLayout for example
import UIKit
class CustomLayout: UICollectionViewFlowLayout
{
override init() {
super.init()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var newAttributes:[AnyObject] = []
override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
super.layoutAttributesForElementsInRect(rect)
var attributes:[AnyObject] = super.layoutAttributesForElementsInRect(rect)!
//arrayWithCapacity(attributes.count)
//configure your attributes for each item hear and store it in separate array and return that array in below example i am sending the same attributes.
return attributes
}
}
in ViewController class
import UIKit
class ViewController: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource {
#IBOutlet weak var aCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var customLayout:CustomLayout = CustomLayout() //initilise the custom layout for collection view
customLayout.minimumLineSpacing = 0.33 //set the offset between items
customLayout.minimumInteritemSpacing = 0.0
customLayout.itemSize = CGSizeMake(50.0, 50.0)
aCollectionView.collectionViewLayout = customLayout //set it to collection view
var cellNib:UINib = UINib(nibName: "CollectionViewCell", bundle: nil)
aCollectionView.registerNib(cellNib, forCellWithReuseIdentifier: "CELL")
}
objective-c version
in xcode crate a new file by subclassing the UICollectionViewFlowLayout lets say it's name as MyCustomCollectionViewFlowLayout and in MyCustomCollectionViewFlowLayout .m file place the code
#import "MyCustomCollectionViewFlowLayout.h"
#implementation MyCustomCollectionViewFlowLayout
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
[super layoutAttributesForElementsInRect:rect];
NSArray *attributes = [super layoutAttributesForElementsInRect:rect];
NSMutableArray *newAttributes = [NSMutableArray arrayWithCapacity:attributes.count];
for (UICollectionViewLayoutAttributes *attribute in attributes)
{
if ((attribute.frame.origin.x + attribute.frame.size.width <= ceil(self.collectionViewContentSize.width)) &&
(attribute.frame.origin.y + attribute.frame.size.height <= ceil(self.collectionViewContentSize.height)))
{
[newAttributes addObject:attribute];
}
}
return newAttributes;
}
- (void)dealloc
{
[super dealloc];
}
#end
and the class where u are using the collection view just import MyCustomCollectionViewFlowLayout.h this and set it to collection view
- (void)viewDidLoad
{
[super viewDidLoad];
//....other codes
MyCustomCollectionViewFlowLayout *flowLayout = [[MyCustomCollectionViewFlowLayout alloc]init];
[flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
//set according to your settings
flowLayout.minimumInteritemSpacing = 0.0f;
flowLayout.minimumLineSpacing = 0.33f; //set the offset between items
_collectionView.pagingEnabled = YES;
_collectionView.bounces = NO;
_collectionView.showsHorizontalScrollIndicator = NO;
_collectionView.showsVerticalScrollIndicator = NO;
[_collectionView setCollectionViewLayout:flowLayout]; //set your custom flow layout hear
[_collectionView registerClass:[MyCollectionViewCell class] forCellWithReuseIdentifier:cellIdentifier]; //set the custom cell
}

Override layout.subviews deletion control in Swift

I am trying to implement similar code to this in a Swift project
https://gist.github.com/joaofranca/3159618
I am having difficulty getting the class for the subview in the NSStringFromClass sections.
I have tried NSStringFromClass(subview.class) but Swift doesn't like it.
Do you know how to use this in Swift?
Thanks,
Andy
Update:
You can call classForCoder on classes derived from NSObject:
var s: NSObject = "hello"
var i: NSObject = 3
NSStringFromClass(s.classForCoder) // "NSString"
NSStringFromClass(i.classForCoder) // "NSNumber"
Original answer:
In Swift, instead of identifying a class by name, use is:
Objective-C:
for (UIView *subview in self.subviews) {
if([NSStringFromClass([subview class]) isEqualToString:#"UITableViewCellDeleteConfirmationControl"]) {
// do magic here
...
}else if ([NSStringFromClass([subview class]) isEqualToString:#"UITableViewCellEditControl"]) {
// do magic here
...
}else if ([NSStringFromClass([subview class]) isEqualToString:#"UITableViewCellReorderControl"]) {
// do magic here
Swift:
for subview in self.subviews as [UIView] {
if subview is UITableViewCellDeleteConfirmationControl {
// do magic here
...
} else if subview is UITableViewCellEditControl {
// do magic here
...
} else if subview is UITableViewCellReorderControl {
// do magic here
Swift 2.0 ->
Override the layoutSubviews()
class MyCustomCell: UITableViewCell {
override func aSubView() {
super.layoutSubviews()
for aSubView in self.subviews {
if String(aSubView.classForCoder).rangeOfString("UITableViewCellDeleteConfirmationView") != nil {
// Do whatever you want to do with default Delete Button.
// aSubView is the Delete Button.
aSubView.frame = CGRectMake(aSubView.frame.origin.x, aSubView.frame.origin.y, aSubView.frame.size.width, aSubView.frame.size.height - 10)
}
}
}
}
swift 3.0 -> using constraints.
override func layoutSubviews() {
super.layoutSubviews()
for aSubView in self.subviews as [UIView] {
if String(describing: aSubView.classForCoder).range(of: "UITableViewCellDeleteConfirmationView") != nil {
aSubView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
aSubView.widthAnchor.constraint(equalToConstant: 50),
aSubView.heightAnchor.constraint(equalToConstant: 50),
])
}
}
}

UICollectionViewCell subclass init never run

I have added a collection view in viewDidLoad like this...
self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, 10, 10) collectionViewLayout:flowLayout];
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
self.collectionView.backgroundColor = [UIColor whiteColor];
[self.collectionView registerClass:[CameraCell class] forCellWithReuseIdentifier:CameraCellReuseIdentifier];
[self.collectionView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:self.collectionView];
And I have a UICollectionViewCell subclass called CameraCell with an init like this...
- (id)init
{
self = [super init];
if (self) {
// Add customisation here...
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(imageChanged:) name:#"image" object:nil];
self.contentView.backgroundColor = [UIColor yellowColor];
self.imageView = [[UIImageView alloc] initWithFrame:CGRectZero];
self.imageView.contentMode = UIViewContentModeScaleAspectFill;
self.imageView.clipsToBounds = YES;
[self.imageView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.contentView addSubview:self.imageView];
NSDictionary *views = NSDictionaryOfVariableBindings(_imageView);
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_imageView]|"
options:0
metrics:nil
views:views]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[_imageView]|"
options:0
metrics:nil
views:views]];
}
return self;
}
But when I run the app the collection view is there and I can scroll it but I can't see any cells. I have added a breakpoint in the cell's init and it never gets called. Is there another method I have to override?
EDIT
When I log the cell in cellForItemAtIndexPath...
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CameraCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CameraCellReuseIdentifier forIndexPath:indexPath];
NSLog(#"%#", cell);
return cell;
}
It displays the correct class...
<CameraCell: 0x1f07ba30; baseClass = UICollectionViewCell; frame = (0 20; 320 280); layer = <CALayer: 0x1f07bb40>>
The method you need to implement is - (instancetype)initWithFrame:(CGRect)rect
I recently tried this in the iOS7 SDK, and I had to override initWithCoder because initWithFrame was never called.
If the cell is loaded from a StoryBoard then you will want to override the initializers like this:
Swift
override init?(coder aDecoder: NSCoder) {
super.init(coder:aDecoder)
//You Code here
}
Objective-C
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
//You code here
}
If you are loading from a nib, then you will want to override the initializers like this:
Swift
override init(frame: CGRect) {
super.init(frame:frame)
//You Code here
}
Objective-C
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
//You code here
}
I know the question is way old, but personally I was lazy and wanted to copy-paste code from somewhere. Unfortunately, there is no 100% correct solution above so I wrote common pattern (for both IB-based and code-based cells)
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
override func awakeFromNib() {
commonInit()
}
private func commonInit() {
}
In my case, when I am using [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, 10, 10) collectionViewLayout:flowLayout] to programatically instantiate a collectionView, initWithFrame never get called, but initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout did.
Swift 5:
required init?(coder aDecoder: NSCoder) {
super.init(coder:aDecoder)
// Your code here.
}

Resources