iCarousels Getting Stuck in iOS 7 - uiscrollview

I have implemented the iCarousels in my project. But after updating the app for iOS 7, my iCarousels are getting stuck in between. Its working fine in iOS 6 and 5. the problem in iOS 7 is that my scroll view below the iCarousel view is getting called first sometimes when I touch my carousel view. Can anyone help me out here?
Is the solution in the following method's return value:
- (CGFloat)carouselItemWidth:(iCarousel *)carousel
I tried many things here, and it works fine for few times, and again after some time it start getting stuck because the scroll view takes the touch from its subview(iCarousel view) and call its own delegate methods before the iCarousel's delegate method.
I am not using any gesture recognizer. I am using scroll view because i have iCarousel view and another view resting on UIScrollView, so that I can use pull to refresh as well.
The following delegate methods I am using, and changing in carouselItemWidth has decreased the stuck problem, but it still persists
- (CATransform3D)carousel:(iCarousel *)carousel itemTransformForOffset:(CGFloat)offset baseTransform:(CATransform3D)transform
{
CGFloat tilt = 0.65f;
CGFloat spacing = 0.28f; // should be ~ 1/scrollSpeed;
CGFloat clampedOffset = fmaxf(-1.0f, fminf(1.5f, offset));
CGFloat itemWidth = 320;
CGFloat x = (clampedOffset * 0.5f * tilt + offset * spacing) * itemWidth;
CGFloat z = fabsf(clampedOffset) * -itemWidth * 0.5f;
transform = CATransform3DTranslate(transform, 0.0f, x, z);
transform = CATransform3DRotate(transform, -clampedOffset * M_PI_2 * tilt, -1.0f, 0.0f, 0.0f);
//DLog(#"offset: %f, %#", offset, [NSValue valueWithCATransform3D:transform]);
return transform;
}
- (NSUInteger)numberOfPlaceholdersInCarousel:(iCarousel *)carousel
{
//note: placeholder views are only displayed on some carousels if wrapping is disabled
return 0;
}
- (CGFloat)carouselItemWidth:(iCarousel *)carousel
{
//usually this should be slightly wider than the item views
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0)
{
return 270;
}
else
{
return 250;
}
}
- (BOOL)carouselShouldWrap:(iCarousel *)carousel
{
return NO;
}

The problem seems to be that when a scrollview receives a touch, it waits a second to see if it should handle it before passing it to the carousel.
You can (mostly) fix this by setting scrollView.delaysContentTouches = NO;
It's still a bit clunky if you try to swipe the carousel when the scrollview is moving/decelerating however. You will have to wait until it stops moving to interact with the carousel.
I'm investigating if there's a better way to fix this.
UPDATE:
I still don't have a proper general-purpose fix for this yet, but as a workaround, you can add this method to your local copy of iCarousel:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gesture shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return [gesture isKindOfClass:[UIPanGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]];
}
This forces iCarousel's pan gesture recogniser to take precedence over the one in the scrollView. If you combine this with the delaysContentTouches fix above, you shouldn't have any issues scrolling the carousel when it's inside a tableview or scrollview.

Related

Scroll Wheel event capture in a Catalyst-converted Mac app

When working with a Mac app which was converted from iOS using Catalyst, the usual ways to capture a mouse's scroll wheel activity for Mac such as with
(void)scrollWheel:(NSEvent *)event;
do not work as NSEvent apparently is not supported when building a Catalyst converted app.
The object I need to control is in a regular image container and not in a scroll view container. I am simply trying to use the scroll wheel to change the loaded image. Trackpad activity works fine but capturing the scroll wheel so far has been elusive.
Thanks!
Use a UIPanGestureRecognizer with allowedScrollTypesMask set to UIScrollTypeMaskDiscrete:
// pan gesture to recognize mouse-wheel scrolling (zoom)
UIPanGestureRecognizer * scrollWheelGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handleScrollWheelGesture:)];
scrollWheelGesture.allowedScrollTypesMask = UIScrollTypeMaskDiscrete; // only accept scroll-wheel, not track-pad
scrollWheelGesture.maximumNumberOfTouches = 0;
[self.view addGestureRecognizer:scrollWheelGesture];
and then
- (void)handleScrollWheelGesture:(UIPanGestureRecognizer *)pan
{
CGPoint delta = [pan translationInView:self.view];
CGFloat zoom = (1000 + delta.y) / 1000;
[self adjustZoomBy:zoom];
}
Swift 5:
func setupScrollWheel() {
let scrollWheelGesture = UIPanGestureRecognizer(target: self, action: #selector(scrollWheelGestureRecognizer(_:)))
scrollWheelGesture.allowedScrollTypesMask = .discrete
scrollWheelGesture.maximumNumberOfTouches = 0
view.addGestureRecognizer(scrollWheelGesture)
}
#objc func scrollWheelGestureRecognizer(_ recognizer: UIPanGestureRecognizer) {
let delta = recognizer.translation(in: view)
}

Label on UIButton not laid out correctly with autolayout

I have an app which I'm updating to auto layout and size classes and I'm getting some weird behaviour with the label on a button.
The button should be a circle and have a label in the centre. I'm implementing my own subclass so I can reuse it.
Here's the storyboard:
and the code for the class which extends UIButton:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self setBackgroundImage:[UIImage imageNamed:#"selected-green"] forState:UIControlStateHighlighted ];
self.layer.borderColor = [UIColor tlbWhiteColor].CGColor;
self.layer.borderWidth = 10;
}
return self;
}
-(void) layoutSubviews {
self.layer.cornerRadius = self.frame.size.width / 2;
}
With this the appears as expected but there is no label. On debugging I see that the frame of the label has 0 height and width. So I extended layoutSubviews:
-(void) layoutSubviews {
self.layer.cornerRadius = self.frame.size.width / 2;
if (self.titleLabel.frame.size.width == 0) {
[self.titleLabel sizeToFit];
[self setNeedsLayout];
[self layoutIfNeeded];
}
}
The label then appears, but it's in the wrong place:
The only extra info I can offer is that in Reveal the button has weird height and width constraints added:
The titleInsets are all at 0.
Help hugely appreciated.
I don't think you need the layoutSubviews override. I think it's a hack and it's hiding your real issue.
What are the constraints on the 'Go' label? How are you centering it in the UIButton? Also what is the height constraint on the label?
My suggestion would be to put both the UIButton and the Go label inside a container view and centre the label inside the container view. The container view should have the same height and width as the UIButton inside it.
Also are you changing the frames of the views programmatically somewhere in the app? Those weird constraints you talk about in reveal point to that.
As per your requirements:
- (void)viewDidLoad {
[super viewDidLoad];
view_1.layer.cornerRadius = 10; //view_1 is white color
view_1.layer.masksToBounds = YES;
view_2.layer.cornerRadius = _view_2.frame.size.width/2;//view_2 is green color
view_2.layer.masksToBounds = YES;
}
output
Ok, this was a real school boy error.
When the docs said 'The implementation of this method is empty before iOS 6' I for some reason took this to mean there's no need to call super on layoutSubviews.
So the fix was:
-(void) layoutSubviews {
[super layoutSubviews]; // <<<<< THIS WAS MISSING
self.layer.cornerRadius = self.frame.size.width / 2;
self.layer.masksToBounds = YES;
}

UIView.animateWithDuration on UITextfield's contentoffset: It clips the text (Swift)

I use the following code:
UIView.animateWithDuration(30, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: {
self.textView.contentOffset = CGPointMake(0, 700)
}, completion: nil)
If the contentOffset is up to about 100, the animation works correctly and all text is visible. Everything higher than that leads to the disappearance of text at the beginning of the textlabel during the animation. The higher the contentOffset, the more text disappears during the animation. So I see white space for a while and then the remaining text comes into animation. Also: Once the animation is done, all text is visible again when I scroll up.
I have tried multiple UITextviews in various superviews. The general idea is a kind of Credits like animation where all text moves slowly upwards for about 30 seconds.
You can try looping small animations together like the following.
func animate(count:Int)
{
if (count > 100)
{
return
}
UIView.animateWithDuration(1, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: {
self.textView.contentOffset = CGPointMake(0, self.textView.contentOffset.y + 10)
}, completion: { finished in
dispatch_async(dispatch_get_main_queue(),{
self.animate(count+1)
})
})
}
This still appears to be an issue even in iOS 10 SDK.
To work around it, I manually animated the scrolling using setContentOffset:, delaying each step using performSelector: withObject: afterDelay: and implementing a method that scrolls one increment at a time (in my case 1/2 point).
To prevent users from manually overriding the scroll animation, you have to manually disable user interaction (animateWithDuration: automatically disables user scrolling while the animation is playing), you also must have scrolling enabled. I also disabled selection and editing, because that fit my use:
note: Objective-C code follows
textBox.editable = NO;
textBox.selectable = NO;
textBox.scrollEnabled = YES;
textBox.userInteractionEnabled = NO;
float contentLength = textBox.contentSize.height - textBox.frame.size.height;
float timePerLine = audioLength / contentLength;
for (float i = 0; i < contentLength; i+=0.5){
[self performSelector:#selector(scrollOneLine) withObject:nil afterDelay:timePerLine * i];
}
And then implemented a method to be used as a selector to scroll one increment at a time
-(void) scrollOneLine {
float currentPosition = textBox.contentOffset.y;
[textBox setContentOffset:CGPointMake(0, currentPosition + 0.5) animated:NO];
}

Soft scroll animation NSScrollView scrollToPoint:

I want to create soft animation between transitions in simply UI:
view that moved
When a call scrollToPoint: for move view to point that transition doesn't animate.
I'm newbe in Cocoa programming (iOS is my background). And I don't know how right use .animator or NSAnimationContext.
Also I was read Core Animation guide but didn't find the solution.
The source can be reach on Git Hub repository
Please help !!!
scrollToPoint is not animatable. Only animatable properties like bounds and position in NSAnimatablePropertyContainer are animated. You don't need to do anything with CALayer: remove the wantsLayer and CALayer stuff. Then with following code it is animated.
- (void)scrollToXPosition:(float)xCoord {
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:5.0];
NSClipView* clipView = [_scrollView contentView];
NSPoint newOrigin = [clipView bounds].origin;
newOrigin.x = xCoord;
[[clipView animator] setBoundsOrigin:newOrigin];
[_scrollView reflectScrolledClipView: [_scrollView contentView]]; // may not bee necessary
[NSAnimationContext endGrouping];
}
Swift 4 code of this answer
func scroll(toPoint: NSPoint, animationDuration: Double) {
NSAnimationContext.beginGrouping()
NSAnimationContext.current.duration = animationDuration
let clipView = scrollView.contentView
clipView.animator().setBoundsOrigin(toPoint)
scrollView.reflectScrolledClipView(scrollView.contentView)
NSAnimationContext.endGrouping()
}
The proposed answers have a significant downside: If the user tries to scroll during an ongoing animation, the input will be cause jittering as the animation will forcefully keep on going until completion. If you set a really long animation duration, the issue becomes apparent. Here is my use case, animating a scroll view to snap to a section title (while trying to scroll up at the same time):
I propose the following subclass:
public class AnimatingScrollView: NSScrollView {
// This will override and cancel any running scroll animations
override public func scroll(_ clipView: NSClipView, to point: NSPoint) {
CATransaction.begin()
CATransaction.setDisableActions(true)
contentView.setBoundsOrigin(point)
CATransaction.commit()
super.scroll(clipView, to: point)
}
public func scroll(toPoint: NSPoint, animationDuration: Double) {
NSAnimationContext.beginGrouping()
NSAnimationContext.current.duration = animationDuration
contentView.animator().setBoundsOrigin(toPoint)
reflectScrolledClipView(contentView)
NSAnimationContext.endGrouping()
}
}
By overriding the normal scroll(_ clipView: NSClipView, to point: NSPoint) (invoked when the user scrolls) and manually performing the a scroll inside a CATransaction with setDisableActions, we cancel the current animation. However, we don't call reflectScrolledClipView, instead we call super.scroll(clipView, to: point), which will perform other necessary internal procedures and then perform reflectScrolledClipView.
Above class produces better results:
Here is a Swift 4 extension version of Andrew's answer
extension NSScrollView {
func scroll(to point: NSPoint, animationDuration: Double) {
NSAnimationContext.beginGrouping()
NSAnimationContext.current.duration = animationDuration
contentView.animator().setBoundsOrigin(point)
reflectScrolledClipView(contentView)
NSAnimationContext.endGrouping()
}
}
I know, it's a little bit off topic, but I wanted to have a similar method to scroll to a rectangle with animation like in UIView's scrollRectToVisible(_ rect: CGRect, animated: Bool) for my NSView. I was happy to find this post, but apparently the accepted answer doesn't always work correctly. It turns out that there is a problem with bounds.origin of the clipview. If the view is getting resized (e.g. by resizing the surrounding window) bounds.origin is somehow shifted against the true origin of the visible rectangle in y-direction. I could not figure out why and by how much. Well, there is also this statement in the Apple docs not to manipulate the clipview directly since its main purpose is to function internally as a scrolling machine for views.
But I do know the true origin of the visible area. It’s part of the clipview’s documentVisibleRect. So I take that origin for the calculation of the scrolled origin of the visibleRect and shift the bounds.origin of the clipview by the same amount, and voilà: that works even if the view is getting resized.
Here is my implementation of the new method of my NSView:
func scroll(toRect rect: CGRect, animationDuration duration: Double) {
if let scrollView = enclosingScrollView { // we do have a scroll view
let clipView = scrollView.contentView // and thats its clip view
var newOrigin = clipView.documentVisibleRect.origin // make a copy of the current origin
if newOrigin.x > rect.origin.x { // we are too far to the right
newOrigin.x = rect.origin.x // correct that
}
if rect.origin.x > newOrigin.x + clipView.documentVisibleRect.width - rect.width { // we are too far to the left
newOrigin.x = rect.origin.x - clipView.documentVisibleRect.width + rect.width // correct that
}
if newOrigin.y > rect.origin.y { // we are too low
newOrigin.y = rect.origin.y // correct that
}
if rect.origin.y > newOrigin.y + clipView.documentVisibleRect.height - rect.height { // we are too high
newOrigin.y = rect.origin.y - clipView.documentVisibleRect.height + rect.height // correct that
}
newOrigin.x += clipView.bounds.origin.x - clipView.documentVisibleRect.origin.x // match the new origin to bounds.origin
newOrigin.y += clipView.bounds.origin.y - clipView.documentVisibleRect.origin.y
NSAnimationContext.beginGrouping() // create the animation
NSAnimationContext.current.duration = duration // set its duration
clipView.animator().setBoundsOrigin(newOrigin) // set the new origin with animation
scrollView.reflectScrolledClipView(clipView) // and inform the scroll view about that
NSAnimationContext.endGrouping() // finaly do the animation
}
}
Please note, that I use flipped coordinates in my NSView to make it match the iOS behaviour.
BTW: the animation duration in the iOS version scrollRectToVisible is 0.3 seconds.
https://www.fpposchmann.de/animate-nsviews-scrolltovisible/

PinthToZoom and Pan of UIView with multiple images

I'm developing an iphone application with an UIView that have an image as a background and multiple buttons with image as background. The image is an image of a country and the buttons are the regions/states of that country.
The problem is that the screen is too much little to display all this data, so I want to enable pinch-to-zoom of the UIView which allows user to have a better view of the country.
Inside 'viewDiDLoad' of my UIViewController, I added:
UIPinchGestureRecognizer *twoFingerPinch = [[[UIPinchGestureRecognizer alloc]
initWithTarget:self
action:#selector(twoFingerPinch:)]
autorelease];
[[self view] addGestureRecognizer:twoFingerPinch];
and I added twoFingerPinch method:
- (void)twoFingerPinch:(UIPinchGestureRecognizer *)recognizer {
if (recognizer.scale > minValue){
CGAffineTransform transform = CGAffineTransformMakeScale(recognizer.scale, recognizer.scale);
self.view.transform = transform;
}
}
and it work correctly, the problem is that the UIView becomes bigger but user can't navigate inside.
So I switched my UIView to a UIScrollView and inside 'twoFingerPinch' method I added:
if (recognizer.state == UIGestureRecognizerStateEnded){
float scaleForView = mLastScale;
UIScrollView *tempScrollView=(UIScrollView *)self.view;
int newWidth = self.view.frame.size.width * scaleForView;
int newHeight = self.view.frame.size.height * scaleForView;
tempScrollView.contentSize=CGSizeMake(newWidth,newHeight);
mLastScale = 1.0;
}
but it doesn't work well: the UISrollView becomes much bigger but it's not bigger as the image scaled and also is not centered where pinchToZoom started.
How can i solve this problem?
Thank You

Resources