I have in a controller:
- (void)viewDidLoad {
[super viewDidLoad];
// Add a scroolView
self.scroolViewDay.scrollEnabled = YES;
// Compute the content Size of the TableDays
self.scroolViewDay.contentSize = CGSizeMake(self.scroolViewDay.frame.size.width,
80 * 48); // TO MODIFY!
[self.scroolViewDay addSubview:self.tableDays];
[self.tableDays setNeedsDisplay];
}
The controller has a XIB where the UIScrollView is into.
The custom view TableDays has a custom drawRect which is never called:
- (void)drawRect:(CGRect)rect {
NSLog(#"sono in drawRect");
}
Why?
-(void) setNeedsDisplay {
[self.subviews makeObjectsPerformSelector:#selector(setNeedsDisplay)];
[super setNeedsDisplay];
}
Add this code, and just override setNeedsDisplay method in your main view and I hope that you know that all of your subviews should be redrawn.
Before adding it programmatically, add it from the storyboard with the correct constraints and see if it getting called.
In my case, there was a problem with the constraints I'm adding to this custom view.
This is the code with the problem,
let header = HeaderBackgroundView(frame: view.bounds)
scrollView.addSubview(header)
header.snp.makeConstraints { make in
make.leading.equalTo(scrollView.snp.leading)
make.trailing.equalTo(scrollView.snp.trailing)
make.top.equalTo(scrollView.snp.top)
make.height.equalTo(200)
}
i fixed it by adding the center x constraint:
header.snp.makeConstraints { make in
make.leading.equalTo(scrollView.snp.leading)
make.trailing.equalTo(scrollView.snp.trailing)
make.top.equalTo(scrollView.snp.top)
make.centerX.equalTo(scrollView.snp.centerX) // this line.
make.height.equalTo(200)
}
I have a UIScrollView, created programmatically, inside of a UIView. What do I need to do to ensure that I can use the delegate method scrollViewDidEndDecelerating?
Here's what I have set up, please assume that within the UIScrollView, that there are three UIImageViews. When the page first loads, I am looking at the center UIImageView and I can scroll once backwards or once forwards. The reason why I need this delegate method is because I intend to use it to calculate which UIImageView I am currently looking at.
ViewController.h
#interface ViewController : UIViewController <UIScrollViewDelegate>
ViewController.m
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
scrollView.pagingEnabled = YES;
[self.view addSubview: scrollView];
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
NSLog(#"scrollViewDidEndDecelerating");
}
Add
scrollView.delegate = self
after the scroll view initialization.
In your delegate method, you can test each uiimageview center to see Which one match the uiview center, That's the uiimageview you are looking at.
So I have a UIScrollView on my iPad app with a single child view (which itself is parent to all the controls). The scrolling all works fine on it. Rotating works fine (the whole view fits in portrait, scrolls on landscape). Once pushing a new screen on the UINavigationController, and then coming back breaks it.
It looks as if the frame of the scrollview's child has moved up, relative to the scroll position, but the scrollview has remained at the bottom (the entire child view has shifted upwards).
I've tried fighting the Constraints in storyboard, literally for hours, and cannot work out what could be causing this.
I had the same problem with scroll view and auto layouts (iOS 6 - doesn't work, iOS 7 - works fine), of course this is not perfect solution, but seems like it works. Hope it will help you:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self performSelector:#selector(content) withObject:nil afterDelay:0.0];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
offset = self.scrollView.contentOffset;
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
self.scrollView.contentOffset = CGPointZero;
}
- (void)content
{
[self.scrollView setContentOffset:offset animated:NO];
}
Get the frame of the subview before it disappears then manually reset the frame of the subview every time the view appears in -(void)viewWillAppear:(BOOL)animated.
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
globalFrameVariable = subview.frame;
[subview removeFromSuperview];
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[subview setFrame:globalFrameVariable];
[scrollView addSubview:subview];
}
Here is a simple solution i found. (Assuming the parent view is meant to span the entire contentSize) Use this subclass of UIScrollView:
#interface BugFixScrollView : UIScrollView
#end
#implementation BugFixScrollView
-(void)layoutSubviews
{
[super layoutSubviews];
UIView *view=[self.subviews firstObject];
if(view)
{
CGRect rect=view.frame;
rect.origin=CGPointMake(0, 0);
view.frame=rect;
}
}
#end
It simply resets the origin every time auto-layout messes it up. this class can be used in InterfaceBuilder simply by changing the class name after placing the UIScrollView.
The title is hard .
The the main case is like this
UIView *superView = [[UIView alloc] initWithFrame:CGRectMake(0,0,400,400)];
UIView *subView = [[UIView alloc] initWithFrame:CGRectMake(-200,-200,400,400)];
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapAction:)];
[subView addGestureRecognizer:tapGesture];
[superView addSubView:subView];
OK , you will find that the tap gesture will take effect when you click the area in (0,0,200,200) , if you click the point (-150,-150) the tap gesture will not take effect.
I don't know whether the click outside the superView bounds to cause this problem or not.
Anyone have any idea how to fix this?
To allow subviews lying outside of the superview to respond to touch, override hitTest:withEvent: of the superview.
Documentation on Event Delivery
Touch events. The window object uses hit-testing and the responder chain to find the view to receive the touch event. In hit-testing, a window calls hitTest:withEvent: on the top-most view of the view hierarchy; this method proceeds by recursively calling pointInside:withEvent: on each view in the view hierarchy that returns YES, proceeding down the hierarchy until it finds the subview within whose bounds the touch took place. That view becomes the hit-test view.
Create a subclass of UIView.
Override hitTest:withEvent.
Use this UIView subclass for the superview.
Add method below in subclass:
(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
NSEnumerator *reverseE = [self.subviews reverseObjectEnumerator];
UIView *iSubView;
while ((iSubView = [reverseE nextObject])) {
UIView *viewWasHit = [iSubView hitTest:[self convertPoint:point toView:iSubView] withEvent:event];
if(viewWasHit) {
return viewWasHit;
}
}
return [super hitTest:point withEvent:event];
}
Note: Reverse enumerator used since subviews are ordered from back to front and we want to test the front most view first.
The only workaround I've found for case like that is to create an instance of a view that is transparent for touches as main view. In such case inner view will respond to touches as it fits bounds of main. In the class I've made from different examples found in the net I can control the level of "touch visibility" like so:
fully visible - all of the touches end up in the view.
only subviews - the view itself invisible, but subviews get their touches.
fully invisible - pretty self explanatory I think :)
I didn't try to use it with gesture recognizers, but I don't think there will be any problem, as it works perfectly with regular touches.
The code is simple...
TransparentTouchView.h
#import <UIKit/UIKit.h>
typedef enum{
TransparencyTypeNone = 0, //act like usual uiview
TransparencyTypeContent, //only content get touches
TransparencyTypeFull //fully transparent for touches
}TransparencyType;
#interface TransparentTouchView : UIView {
TransparencyType _transparencyType;
}
#property(nonatomic,assign)TransparencyType transparencyType;
#end
TransparentTouchView.m
#import "TransparentTouchView.h"
#implementation TransparentTouchView
#synthesize
transparencyType = _transparencyType;
- (id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor clearColor];
}
return self;
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
// UIView will be "transparent" for touch events if we return NO
switch (_transparencyType) {
case TransparencyTypeContent:
for(UIView* subview in self.subviews){
CGPoint p = [subview convertPoint:point fromView:self];
if([subview pointInside:p withEvent:event]){
return YES;
}
}
return NO;
break;
case TransparencyTypeFull:
return NO;
default:
break;
}
return YES;
}
#end
I believe that you can accomodate it to your needs.
When a UITextField embedded in a UIScrollView becomes the first responder (for example, by the user typing in some character), the UIScrollView scrolls to that Field automatically. Is there any way to disable that?
Duplicate rdar://16538222
Building on Moshe's answer... Subclass UIScrollView and override the following method:
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated
Leave it empty. Job done!
In Swift:
class CustomScrollView: UIScrollView {
override func scrollRectToVisible(_ rect: CGRect, animated: Bool) { }
}
I've been struggling with the same problem, and at last I've found a solution.
I've investigated how the auto-scroll is done by tracking the call-trace, and found that an internal [UIFieldEditor scrollSelectionToVisible] is called when a letter is typed into the UITextField. This method seems to act on the UIScrollView of the nearest ancestor of the UITextField.
So, on textFieldDidBeginEditing, by wrapping the UITextField with a new UIScrollView with the same size of it (that is, inserting the view in between the UITextField and it's superview), this will block the auto-scroll. Finally remove this wrapper on textFieldDidEndEditing.
The code goes like:
- (void)textFieldDidBeginEditing:(UITextField*)textField {
UIScrollView *wrap = [[[UIScrollView alloc] initWithFrame:textField.frame] autorelease];
[textField.superview addSubview:wrap];
[textField setFrame:CGRectMake(0, 0, textField.frame.size.width, textField.frame.size.height)];
[wrap addSubview: textField];
}
- (void)textFieldDidEndEditing:(UITextField*)textField {
UIScrollView *wrap = (UIScrollView *)textField.superview;
[textField setFrame:CGRectMake(wrap.frame.origin.x, wrap.frame.origin.y, wrap.frame.size.width, textField.frame.size.height)];
[wrap.superview addSubview:textField];
[wrap removeFromSuperview];
}
hope this helps!
I had the same issue with disabling auto-scrolling of a UITextView being a cell of UITableView. I was able to resolve it using the following approach:
#interface MyTableViewController : UITableViewController<UITextViewDelegate>
#implementation MyTableViewController {
BOOL preventScrolling;
// ...
}
// ... set self as the delegate of the text view
- (void)textViewDidBeginEditing:(UITextView *)textView {
preventScrolling = YES;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (preventScrolling) {
[self.tableView setContentOffset:CGPointMake(0, -self.tableView.contentInset.top) animated:NO];
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
preventScrolling = NO;
}
Defining scrollViewWillBeginDragging is used for restoring the default scrolling behaviour, when the user himself initiates scrolling.
As Taketo mentioned, when a UITextField is made first responder, its first parent view that is of type UIScrollView (if one exists) is scrolled to make that UITextField visible. The easiest hack is to simply wrap each UITextField in a UIScrollView (or ideally, wrap all of them in a single dummy UIScrollView). This is very similar to Taketo's solution, but it should give you slightly better performance, and it will keep your code (or your interface in Interface Builder) much cleaner in my opinion.
Building on Luke's answer, to handle the issue that his solution completely disables auto-scroll, you can disable it selectively as follows:
// TextFieldScrollView
#import <UIKit/UIKit.h>
#interface TextFieldScrollView : UIScrollView
#property (assign, nonatomic) IBInspectable BOOL preventAutoScroll;
#end
#implementation TextFieldScrollView
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated {
if (self.preventAutoScroll == NO) {
[super scrollRectToVisible:rect animated:animated];
}
}
#end
This way, you can completely set it up in Interface Builder to disable the auto-scroll, but have full control at any time to re-enable it (though why you'd want to is beyond me).
It looks like UIScrollview which contains UITextfield, auto adjusts its content offset; when textfield is going to become first responder.
This can be solved by adding textfield in scrollview of same size first, and then adding in to main scroll view. instead of directly adding in to main scrollview
// Swift
let rect = CGRect(x: 0, y: 0, width: 200, height: 50)
let txtfld = UITextField()
txtfld.frame = CGRect(x: 0, y: 0, width: rect.width, height: rect.height)
let txtFieldContainerScrollView = UIScrollView()
txtFieldContainerScrollView.frame = rect
txtFieldContainerScrollView.addSubview(txtfld)
// Now add this txtFieldContainerScrollView in desired UITableViewCell, UISCrollView.. etc
self.mainScrollView.addSubview(txtFieldContainerScrollView)
// Am33T
This is the way I do it:
It is very simple, you get to return your own contentOffset for any scrollRectToVisible.
This way you are not harming the normal behaviour and flow of things - just providing the same functionality in the same channel, with your own improvements.
#import <UIKit/UIKit.h>
#protocol ExtendedScrollViewDelegate <NSObject>
- (CGPoint)scrollView:(UIScrollView*)scrollView offsetForScrollingToVisibleRect:(CGRect)rect;
#end
#interface ExtendedScrollView : UIScrollView
#property (nonatomic, unsafe_unretained) id<ExtendedScrollViewDelegate> scrollToVisibleDelegate;
#end
#import "ExtendedScrollView.h"
#implementation ExtendedScrollView
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated
{
if (_scrollToVisibleDelegate && [_scrollToVisibleDelegate respondsToSelector:#selector(scrollView:offsetForScrollingToVisibleRect:)])
{
[self setContentOffset:[_scrollToVisibleDelegate scrollView:self offsetForScrollingToVisibleRect:rect] animated:animated];
}
else
{
[super scrollRectToVisible:rect animated:animated];
}
}
#end
I've tried #TaketoSano's answer, but seems not works.. My case is that I don't have a scrollview, just a view with several text fields.
And finally, I got a workaround. There're two default notification names for keyboard that I need:
UIKeyboardDidShowNotification when the keyboard did show;
UIKeyboardWillHideNotification when the keyboard will hide.
Here's the sample code I used:
- (void)viewDidLoad {
[super viewDidLoad];
...
NSNotificationCenter * notificationCetner = [NSNotificationCenter defaultCenter];
[notificationCetner addObserver:self
selector:#selector(_keyboardWasShown:)
name:UIKeyboardDidShowNotification
object:nil];
[notificationCetner addObserver:self
selector:#selector(_keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
- (void)_keyboardWasShown:(NSNotification *)note {
[self.view setFrame:(CGRect){{272.f, 55.f}, {480.f, 315.f}}];
}
- (void)_keyboardWillHide:(NSNotification *)note {
[self.view setFrame:(CGRect){{272.f, 226.5f}, {480.f, 315.f}}];
}
Here, the (CGRect){{272.f, 226.5f}, {480.f, 315.f}} is view's default frame when keyboard is hidden. And (CGRect){{272.f, 55.f}, {480.f, 315.f}} is view's frame when keyboard did show.
And b.t.w., the view's frame changing will be applied animation automatically, this's really perfect!
I have a collection view with a text field at the very top, mimicking the UITableView.tableHeaderView. This text field is located in the negative content offset space so that it doesn't interfere with the rest of the collection view. I basically am detecting whether or not the user is performing the scrolling in the scroll view and whether or not the text field is first responder and if the scroll view is being scrolled beyond the top of the scroll view's content inset. This exact code won't necessarily help anyone but they could manipulate it to fit their case.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// This is solving the issue where making the text field first responder
// automatically scrolls the scrollview down by the height of the search bar.
if (!scrollView.isDragging && !scrollView.isDecelerating &&
self.searchField.isFirstResponder &&
(scrollView.contentOffset.y < -scrollView.contentInset.top)) {
[scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, -scrollView.contentInset.top) animated:NO];
}
}
I don't know of any property of UIScrollView that would allow that. It would be poor user experience to be able to disable that, IMHO.
That said, it may be possible to subclass UIScrollView and override some of its methods to check that the UITextfield is not a first responder before scrolling.
An easier way to stop the scrollview scrolling when you select a textField is in your viewController::viewWillAppear() DO NOT call [super viewWillAppear];
You can then control the scroll as you wish.