UIView inside an UIScrollView did not refresh itself despite call of method "setNeedDisplay" - ios

I've got an UIScrollView with inside, some views loaded from a xib file.
The UIScrollView loads only three Views. The current, the left one and the right one.
For exemple, I have one view at the left and one view at the right of the current View. If I scroll to the right, the UIScrollView will delete the view to the left, scroll to the right to the new current View and load the new view to the right of the new current View.
In addition, I have a button outside the UIScrollView. When I click on it, it changes the background color of the current view displayed on the UIScrollView.
It works well but sometimes, I don't know why, when I click on the button to change the background color of the view is well changed, but the view is not refresh so the user can't see the change of background color.
The UIScrollView:
container = [[UIScrollView alloc] initWithFrame:frame];
[container setBackgroundColor:[UIColor clearColor]];
[container setCanCancelContentTouches:NO];
[container setClipsToBounds:NO];
[container setShowsHorizontalScrollIndicator:NO];
[container setPagingEnabled:YES];
The method call when I click on the button to change the background color of the current view
- (void)menuColor:(MenuPickerViewController *)controller didPickOption:(UIView *)button
{
// Get the object containing the data of the product linked with the view.
MyProduct *product = (MyProduct *)[MyProduct getProduct:[_slider getCurrentContentDisplayed]];
// Get the the superview of the button sender to have an access for the attributes of this button (color selected, ...)
ColorButtonMenu *colorView = (ColorButtonMenu *)[button superview];
// Get the current UIView displayed in the UIScrollView
MyView *myView = (MyView *)[self sliderGetViewWithID:[_slider getCurrentContentDisplayed] FromSlider:_slider];
// I check with debugger, the color is well setted
product.color = colorView.color;
// "border" is a view in my xib that I want change its background color.
IFPrint(#"myView.border backgorund color before: %#\n", myView.border.backgroundColor.description);
[myView.border setBackgroundColor:[Utilities colorFromHexString:colorView.color]];
[myView.border setNeedsDisplay];
IFPrint(#"myView.border backgorund color after: %#\n", myView.border.backgroundColor.description);
IFPrint(#"=== DEBUG ===\n");
IFPrint(#"isMainThread ? : %i\n", [NSThread isMainThread]); // Always return YES
IFPrint(#"myView: %#\n", myView); // Always return the address of a valid pointer
IFPrint(#"myView border: %#\n", myView.border); // Always return the address of a valid pointer
IFPrint(#"=============\n\n");
}
So, as you can see at the end of the method, I tried to call method setNeedsDisplay on the view loaded from a xib and the other view inside "border" but nothings work.
Moreover, my method is always called on the main thread.
Any suppositions ?
Thanks !
Edit:
Obviously, I have tested if the view return by sliderGetViewWithID is the correct view. All the attributes are well set. In my opinion it's truly a refresh problem.

are you sure the view you're trying to set background color for is actually visible? I guess it might be offscreen so you see nothing happening.
You might want to trap that event by finding the intersection between scrollview's bounds and myView's frame. if there's no intersection, that means myView is not actually visible.
so the code is:
BOOL intersects = CGRectIntersectsRect(scrollview.bounds, myView.frame);
if(intersects == NO)
{
NSLog(#"myView is offscreen");
}

Problem solved :
After setting the background color of the view. I remove the view from the UIScrollView and I re add it inside the UIScrollView. It's a bit tricky but it works good !
[myView removeFromSuperView];
[myScrollView addSubview:myView];

Related

How to prevent iOS from resizing your UIViewController's view after return from background

I have a UIViewController that displays a form with several text fields. In order to prevent the text fields from getting blocked by the keyboard, I resize the controller's view when the keyboard appears and disappears.
However, when the keyboard is up, the user presses the home button, and then returns to the app, the controller's view will be resized again to the size it was before the keyboard was up and the keyboard will still be showing.
What's causing my controller's view to be resized on return from background, and how can I prevent it?
Maybe you need to nest a UIView,for example
_backgroundView = [UIView new];
_backgroundView.backgroundColor = [UIColor whiteColor];
_backgroundView.frame = CGRectZero;
[self.view addSubview:_backgroundView];
[_backgroundView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.top.mas_equalTo(self.view);
make.height.mas_equalTo(self.view.mas_height);
}];
then you need add your custom UIView to this backgroundView.
as you said,UIViewController's view will be resized after return from background. so you can nest a UIView of the same size as self.view,and add your custom UIView to this UIView.
In order to prevent the text fields from getting blocked by the keyboard, you can resize this backgroundView when the keyboard appears and disappears. and this time when you click the home button to enter the background or return from background,self.view won't be resized and backgroundView won't be resized too.
Although it is a bit of a hassle, this will solve your problem and will not affect the user experience anymore. And if you have a better solution, please let me know
It sounds like you are setting the frame and not using autolayout. When the view reappears viewDidLayoutSubviews gets called and your frame gets recalculated obliterating your previous change. You can either:
1) Move your frame to viewDidLayoutSubviews and change its size only if the keyboard is showing.
2) Use autolayout and simply pull up your bottom constraint .constant by an amount equal to your keyboard height.
In both cases you should call layoutIfNeeded to trigger autolayout/viewDidLayoutSubviews when the keyboard appears/disappears. This behavior is a good example of why you should not manipulate your frames outside of viewDidLayoutSubviews except for transitory animations.

Bounds automatically changes on UIScrollView with content insets

I'm using a UIScrollView as my paging scroll view, pagesScrollView. Inside that, I put individual UIScrollViews which are used exclusively for zooming. Inside each of those, I have one view which is the page item which should be zoomable. All of that is inside a UINavigationController with a translucent navbar.
My pagesScrollView has contentInset.top = 64 and bounds.origin.y = -64 (that seems weird to me, but that's what the system is setting automatically for me), and this works just fine. My screen looks great!
However, after I scroll the pagesScrollView even a tiny bit, as soon as scrollViewWillEndDragging is called, the pagesScrollView begins an animated change from bounds.origin.y = -64 to bounds.origin.y = 0 which causes my page items to be obscured by the navbar.
On the left is what it looks like when it loads, on the right is what it looks like after I drag just a few pixels and then let go, it slides up under the navbar (because the bounds.origin.y goes to 0).
The problem is that I don't have any code that is altering the bounds and I don't have any code in the various scroll delegate methods that do anything. I've added a bunch of scroll delegate methods and just added NSLog()s so I can figure out when/where the change is happening, but it's not happening anywhere in my code.
So, I don't know what code I can show you to help you help me.
EDIT: I built a new project from scratch to remove all other variables.. I put a bare UIViewController into a UINavigationController. I put a UIScrollView into my View the entire size of the view. The following code is the entire project.
It turns out the issue (described below) only appears once PAGING IS ENABLED on the UIScrollView! Wtf? :)
Here is a link to download a basic project with only a few lines of code which demonstrates the problem. Just click in the scrollview and you'll see it shift up as the bounds change. http://inadaydevelopment.com/stackoverflow/WeirdScrollViews.zip
How can I have paging enabled on my scrollview without the bounds freaking out during scrolling and shifting everything under the nav bar?
It's possible to set the navbar to opaque and the problem is avoided, but the ideal is to have standard iOS7 behavior so that after the content view is zoomed, THEN the content is allowed to be under the navbar and should show through the translucency normally.
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSArray *colors = #[
[UIColor blueColor],
[UIColor orangeColor],
[UIColor magentaColor],
];
NSArray *zoomerColors = #[
[UIColor greenColor],
[UIColor yellowColor],
[UIColor purpleColor],
];
self.scroller.pagingEnabled = YES;
[self.scroller setContentSize:CGSizeMake(self.scroller.frame.size.width*colors.count, self.scroller.frame.size.height)];
CGRect subviewFrame = CGRectMake(0, 0, 160, 240);
for (int index=0; index < colors.count; index++) {
UIColor *color = [colors objectAtIndex:index];
UIColor *zoomerColor = [zoomerColors objectAtIndex:index];
UIView *subview = [[UIView alloc] initWithFrame:subviewFrame];
subview.backgroundColor = color;
CGRect zoomerFrame = CGRectMake(index*self.scroller.frame.size.width, 0, self.scroller.frame.size.width, self.scroller.frame.size.height);
UIScrollView *zoomer = [[UIScrollView alloc] initWithFrame:zoomerFrame];
[zoomer addSubview:subview];
zoomer.backgroundColor = zoomerColor;
[self.scroller addSubview:zoomer];
}
}
Just switch off Adjust Scroll View Insets
It's an iOS bug. I created the following subclass of UIScrollView to get a log of what happens to y over time and who was pushing it:
#implementation CSScrollView
- (void)setContentOffset:(CGPoint)contentOffset
{
NSLog(#"%0.0f %#", contentOffset.y, [NSThread callStackSymbols]);
NSLog(#"[%#]", self.layer.animationKeys);
[super setContentOffset:contentOffset];
}
#end
(and changed the view class in the storyboard)
When you release your finger, a method called UIScrollView _smoothScrollDisplayLink: starts animating the scroll view to its final position. As per the second log, there's no CAAnimation involved, the scroll view uses its own display link to do its own transition. That custom code appears to make the mistake of animating from y = whatever to y = 0, failing to take the content offset into account.
As a proof-of-concept hack I changed the code to:
#implementation CSScrollView
- (void)setContentOffset:(CGPoint)contentOffset
{
contentOffset.y = -64.0f;
[super setContentOffset:contentOffset];
}
#end
And, unsurprisingly, the problem went away.
You probably don't want to hard code the -64.0f but I'd conclude:
it's an iOS bug;
work around it by rejecting nonsensical values via a subclass of UIScrollView with a suitable custom implementation of - setContentOffset:.
A sensible generic means might be to check the state of self.panGestureRecognizer — that'll allow you to differentiate between scrolls the user is responsible for and other scrolls without relying on any undocumented API or complicated capturing of delegate events. Then if necessary crib the correct contentOffset.y from the current value rather than hardcoding it.
My pagesScrollView has contentInset.top = 64 and bounds.origin.y = -64 (that seems weird to me, but that's what the system is setting automatically for me), and this works just fine. My screen looks great!
It because of iOS 7 sets contentInset.top to 64 on all scrollviews.
Just add this line of code into your view controller and all will work as expected:
-(UIRectEdge)edgesForExtendedLayout {
return UIRectEdgeNone;
}
I checked on your example project.
I have checked you example use below code in viewController.m file
-(void)viewDidLoad
{
if ([[UIDevice currentDevice] systemVersion].floatValue>=7.0) {
self.edgesForExtendedLayout = UIRectEdgeNone;
}
}
It's working fine...
It turns out the issue (described below) only appears once PAGING IS ENABLED on the UIScrollView! Wtf? :)
As you said that, If you enable the scroll paging, the UIScrollView will stop at a paging edge after a dragging or any movement, which is promised by the framework. Bounds.origin.y set by zero means that the first page edge matched the scroll view frame edge, cuz you have 64 contentInsets there. So that's not bug, that is what it is. And since your bar is translucent, remember where is your scroll view's frame edge, it's under the bar. In a word, this is not a bug, I think, but a effect of scroll paging.

iOS 7 UIScrollView doesn't scroll when presented as modal view controller, works fine otherwise

I have a storyboard in which I have a view controller, (InfoViewController) in which I have an UIScrollView with some labels, uitextviews, etc. this is all created in IB, no code has been written at all. The only thing that is left for me to do is to set the content size, which I do as following:
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
CGRect screenRect = [[UIScreen mainScreen] bounds];
[self.scrollView setContentSize:CGSizeMake(screenRect.size.width, 600)];
[self.scrollView setBackgroundColor:[UIColor greenColor]];
}
Whenever I make this view the entry point of my app, it works perfectly. I can see my view, the content size is set, the background color is being set to green.
Now it comes, I created another view controller, and this view controller is now my entry point of the app. I added a button in there, and on this button I did a "modal segue" to the earlier mentioned Info View Controller.
When I now run my app, I press this button, my Info View Controller shows up. The green background color is being set, but it's impossible to scroll. So the code is being executed (otherwise the background color couldn't been green, in the storyboard it's just plain white) but somehow whenever I use this "modal segue", the scroll functionality gets lost.
How can I fix this?
Try to insert a UIView into the scroll view...
Set the UIView with top, bottom, leading and trailing space to super view to 0.
Then insert everything into the UIView rather than into the ScrollView
Then modify the constraint height of the inner UIView instead of the contentsize of the scroll view, it works with iOS7

uitableview with header like instagram user profile

I've been struggling with this for quite a while now.
I have to implement an user profile similar to what Instagram has in their ios app.
When clicking on the first to buttons on that tab bar like thing all the contents downwards from it changes. The tableview that is displayed on the bottom part has dynamic size so they keep account of that also.
I have something implemented where the top part is a UIView with 5 buttons and based on them the bottom part (witch is like a container view) changes content. And these two (top uiview and bottom container view) are part of UIScrollView. But this way I can't get information back in time on the size about the tableview's size that I want to display in the bottom part in order to modify the UIScrollView's size. And I have a feeling this is a flawed way to do it.
I would really appreciate any ideas oh how to implement this king of interaction. Thank you.
I believe it's a headerView on a UITableView or a UICollectionView, depending on which view mode you have selected. When you tap one of the buttons it changes out the UITableView to a UICollectionView or vice versa.
You want to keep track of the current contentOffset for whichever is being displayed (UICollectionView and UITableView are both subclasses of UIScrollView so you will be able to get this from both) and then set the contentOffset on the view you're switching to.
Setup an ivar for the UIView header subclass so you can easily re-use it.
This is what I have. My problem is that I'm mot getting back in useful time the tableview's frame height from the tableview controller to the UserProfileViewController in order to change the latter's scrollview size. I also feel that I'm somehow doing this backwards so any suggestions are more than welcome.
This view has two parts: an upper part and a lower part. The parent view is a scroll view. What I wanted to achieve with this is having a sort of tab bar in the upper part that will controll waht will appear in the lower part.
The upper part has a flip animation when the upper left button is pressed to reveal another view.
The way this is achieved is by having 2 views: a dummy view and the back view. The dummy view has the front view as a child. The front view is the one that containes all the buttons.
The code for this animation is achieved in this way:
- (IBAction)infoButtonPressed:(id)sender
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1.0];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.hoverView cache:YES];
if ([self.headerView superview]) {
[self.headerView removeFromSuperview];
[self.hoverView addSubview:self.backOfHeaderView];
[self.infoButton removeFromSuperview];
[self.backOfHeaderView addSubview:self.infoButton];
} else {
[self.backOfHeaderView removeFromSuperview];
[self.hoverView addSubview:self.headerView];
[self.infoButton removeFromSuperview];
[self.headerView addSubview:self.infoButton];
}
[UIView commitAnimations];
}
The lower part is made out of a container view that acts as a place holder.
When a button is pressed a different view controller is displayed in the container view.
Each view controller has a container view of it's own. The specific view of that view controller (tableview) is added to it's container view when the controller is loaded. It also makes sure that if the tableview is already added to the container view it will be removed. All this is done in each specific view controller.
In the view controller of the User Profile view there is an instance of the container view and one of a UIViewController that also acts as a placeholder(named currentViewController from now on). When a specific button is pressed it checks if the an instance of the view controller that we want to display already exists. If not it will make one and will set it's tableview's frame to the bounds of the container view. After that it will remove the currentViewController's view from the superview and the currentViewController itself from the parent viewcontroller to make sure that if there is something assigned to these they will not be there. Then it goes and assigns the desired viewcontroller to the currentViewController. It also assigns the desired viewcontroller's containerView instance to the containerview in the parent viewcontroller (the User Profile viewcontroller). At the end it will add the desired viewcontroller as a child to the main viewcontroller (the User Profile viewcontroller) and desired viewcontroller's view to the containerView of the main viewcontroller.
This is the code for one of the buttons:
//Check if there is an instance of the viewcontroller we want to display. If not make one and set it's tableview frame to the container's view bounds
if(!_userWallViewController) {
self.userWallViewController = [[WallViewController alloc] init];
// self.userWallViewController.activityFeedTableView.frame = self.containerView.bounds;
}
[self.userWallViewController.containerView addSubview:self.userWallViewController.activityFeedTableView];
//If the currentviewcontroller adn it's view are already added to the hierarchy remove them
[self.currentViewController.view removeFromSuperview];
[self.currentViewController removeFromParentViewController];
//Add the desired viewcontroller to the currentviewcontroller
self.currentViewController = self.userWallViewController;
//Pass the data needed for the desired viewcontroller to it's instances
self.userWallViewController.searchURLString = [NSString stringWithFormat:#"event/user/%#/", self.userID];
self.userWallViewController.sendCommentURLString = [NSString stringWithFormat:#"event/message/%#", self.userID];
self.userWallViewController.totalCellHeight = ^(float totalCellHeight){
self.userWallViewController.numberOfCells = ^(float numberOfCells){
NSLog(#"The total number of cells: %f", numberOfCells);
NSLog(#"The total cell height: %f", totalCellHeight);
self.scrollView.contentSize = CGSizeMake(320.0, totalCellHeight + 172.0 + 33.0);
CGRect newFrame = self.userWallViewController.containerView.frame;
newFrame.size.height = totalCellHeight + 33.0;
self.userWallViewController.containerView.frame = newFrame;
NSLog(#"Container view: %f", self.containerView.frame.size.height);
NSLog(#"Scroll view: %f",self.scrollView.contentSize.height );
};
};
//Add this containerview to the desired viewcontroller's containerView
self.userWallViewController.containerView = self.containerView;
//Add the needed viewcontroller and view to the parent viewcontroller and the containerview
[self addChildViewController:self.userWallViewController];
[self.containerView addSubview:self.userWallViewController.view];
[self performSelector:#selector(changeScrollView) withObject:self afterDelay:0.5];
//CLEAN UP THE CONTAINER VIEW BY REMOVING THE PREVIOUS ADDED TABLE VIEWS
[self.userFansViewController.userSimpleTableView removeFromSuperview];
[self.fanOfViewController.userSimpleTableView removeFromSuperview];
[self.userPublishedMovellaListViewController.gridView removeFromSuperview];
[self.userPublishedMovellaListViewController removeFromParentViewController];
self.userPublishedMovellaListViewController = nil;
}
I know this answer is over a year late, but I wanted to state my hypothesis on it...just incase it might help someone else later. Im implementing a similar view and came to this conclusion. Anyone is welcomed to correct me if I'm wrong.
I think that perhaps the top view is a header view and the two options that seem like a collection view and a table view are both collection views.
Because the layout of collection views can be fine tuned to the most minute details, I think the view that looks like a table view is just a really specifically designed collection view. And when switching between the views, the collection view's data and properties are being swapped and reloaded.

Reordering UIView subviews

In my app I am trying bring a subview to front, then put it back to its original layer position later. The code should be pretty simple:
To bring the subview to front (inside my custom UIView class):
[self.superview bringSubviewToFront:self];
Easy. I store the original z position in an instance variable called, you guessed it, zPosition. So, the line before -bringSubviewToFront: is:
zPosition = [self.superview.subviews indexOfObject:self];
So, all of the code I use to bring my subview to front is:
zPosition = [self.superview.subviews indexOfObject:self];
[self.superview bringSubviewToFront:self];
This works as it should. The problem is when I try to put the subview back where it was. I'm simply doing this:
[self.superview exchangeSubviewAtIndex:zPosition withSubviewAtIndex:
[self.superview.subviews indexOfObject:self]];
Using this code, if I have two subviews, this is what happens:
Let's say I have view A and view B. View A is above view B. I tap view B, it comes to the front. I tap view B again (it should go back to where it was), and nothing happens, so it's now on view A. If I now tap view A, it comes to the front, but when I tap it again (so it should go back to its original z position: below view B), all of its sibling views disappear!
Does anyone see what could be causing this problem?
There is no need to remove from superview:
[self.superview insertSubview:self atIndex:zPosition];
exchangeSubviewAtIndex may well put the view back in the right place, but it will also swap another view on top, which wont be what you started with. You might need to do something like this instead of exchangeSubviewAtIndex :
[self retain];
UIView *superview = self.superview;
[self removeFromSuperview];
[superview insertSubview:self atIndex:zPosition];
[self release];
[ Swift solution ]
As other guys said, there is no need to remove and re-add your subviews.
Instead I've found that the most convenient method is:
superView.insertSubview(subviewYouWantToReorder, aboveSubview: subviewWhichShouldBeBelow)
This question and answers were very helpful to me.
I had the requirement to place an overlay between the viewstack which views are above and below the overlay, and i wanted to keep it dynamic.
That is, a view can tell it is hidden or not.
I used the following algorithm to reorder the views.
Thanks to AW101 below for the "No need to remove view".
Here is my algorithm:
- (void) insertOverlay {
// Remember above- and belowcounter
int belowpos = 0, abovepos = 0;
// Controller mainview
UIView *mainview = [self currentMainView];
// Iterate all direct mainview subviews
for (UIView* view in mainview.subviews) {
if ([self isAboveOverlay:view]) {
// Re-insert as aboveview
[mainview insertSubview:view atIndex:belowpos + (abovepos++)];
}
else {
// Re-insert as belowview
[mainview insertSubview:view atIndex:belowpos++];
}
}
// Put overlay in between above and below.
[mainview insertSubview:_overlay atIndex:belowpos];
}

Resources