I'm using the pod iOS-Slide-Menu repo in one of my personal projects.
If I run my project on iOS < 11 everything works as expected: When the side menu (blue view controller), touching outside it, i.e. the green part. Automatically closes it
But running on a device with iOS 11 the menu does't get closed when tapping outside.
Another curious situation is that this only happen with recently compiled versions (currently using Xcode 9.0), running the App Store version on a iOS11 device also works correctly.
So my questions are:
Why is this happening?
How can I avoid this to happen without replacing the whole library?
I created a SAMPLE PROJECT in github to reproduce the problem.
In your SlideNavigationController.m file go to viewWillLayoutSubviews method and remove or comment below line
[self enableTapGestureToCloseMenu:NO];
It is because viewWillLayoutSubviews method gets called in ios 11 initially!
so, from viewWillLayoutSubviews, enableTapGestureToCloseMenu gets called and it is removing gesture recognizer from right menu!
Hello #Adrime i have downloaded your code and tested in Xcode 9.0
found same issue like you have. outside tapped not closed view.
after seeing library i have found one solution.
In SlideNavigationController.m file,
one method is already created which is - (void)enableTapGestureToCloseMenu:(BOOL)enable
in that method, just comment this one line [self.view removeGestureRecognizer:self.tapRecognizer];
and your problem is solved.
this line removedGesture of tapping outside.
Updated:
I got it what you want, just change your viewWillLayoutSubviews method
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
// Update shadow size of enabled
if (self.enableShadow)
self.view.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.view.bounds].CGPath;
// When menu open we disable user interaction
// When rotates we want to make sure that userInteraction is enabled again
//[self enableTapGestureToCloseMenu:NO];
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"7.0"))
self.interactivePopGestureRecognizer.enabled = YES;
self.topViewController.view.userInteractionEnabled = YES;
if (self.menuNeedsLayout)
{
[self updateMenuFrameAndTransformAccordingToOrientation];
// Handle different horizontal/vertical slideOffset during rotation
// On iOS below 8 we just close the menu, iOS8 handles rotation better so we support keepiong the menu open
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"8.0") && [self isMenuOpen])
{
Menu menu = (self.horizontalLocation > 0) ? MenuLeft : MenuRight;
[self openMenu:menu withDuration:0 andCompletion:nil];
}
self.menuNeedsLayout = NO;
}
}
What I Did : Do comment //[self enableTapGestureToCloseMenu:NO]; code and put below code
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"7.0"))
self.interactivePopGestureRecognizer.enabled = YES;
self.topViewController.view.userInteractionEnabled = YES;
OLD:
It's Because in SlideNavigationController.m file you are calling [self enableTapGestureToCloseMenu:NO];
Just remove it or make condition for iOS 11 to pass YES for all
For EX.
if (IOS_VERSION == 11) {
[self enableTapGestureToCloseMenu:YES];
}
else {
[self enableTapGestureToCloseMenu:NO];
}
Because when you open your slide menu or click on green area alway call
[self enableTapGestureToCloseMenu:NO];
So as per method code
- (void)enableTapGestureToCloseMenu:(BOOL)enable
{
if (enable)
{
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"7.0"))
self.interactivePopGestureRecognizer.enabled = NO;
self.topViewController.view.userInteractionEnabled = NO;
[self.view addGestureRecognizer:self.tapRecognizer];
}
else
{
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"7.0"))
self.interactivePopGestureRecognizer.enabled = YES;
self.topViewController.view.userInteractionEnabled = YES;
[self.view removeGestureRecognizer:self.tapRecognizer];
}
}
It's removeGestureRecognizer for view.
Related
My code runs perfectly fine on iOS 10.3.3, whereas when I run the same code on iOS 11.2.1, it causes a crash at launch time with the following error:
Assertion failure in -[_UINavigationBarVisualProviderModernIOS _contentViewFittingHeight], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3698.33.7/_UINavigationBarVisualProviderModernIOS.m:569
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Sigh. Contentview size is zero.'
I cleaned the code, cleared the derived data, but those did not solve the issue.
There was an issue in the library I was using for Slide menu "iOS-Slide-Menu". So, I simply changed this two very methods in the library which then worked fine.
- (void)setup
{
[[NSUserDefaults standardUserDefaults] setObject:nil forKey:#"ssidName"];
[[NSUserDefaults standardUserDefaults] synchronize];
if (singletonInstance)
NSLog(#"Singleton instance already exists. You can only instantiate one instance of SlideNavigationController. This could cause major issues");
singletonInstance = self;
self.menuRevealAnimationDuration = MENU_SLIDE_ANIMATION_DURATION;
self.menuRevealAnimationOption = MENU_SLIDE_ANIMATION_OPTION;
}
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
// Update shadow size of enabled
if (self.enableShadow)
self.view.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.view.bounds].CGPath;
self.landscapeSlideOffset = self.view.frame.size.width/6;
self.portraitSlideOffset = self.view.frame.size.width/6;
self.panGestureSideOffset = 0;
self.avoidSwitchingToSameClassViewController = YES;
self.enableShadow = YES;
self.enableSwipeGesture = NO;
self.delegate = self;
// When menu open we disable user interaction
// When rotates we want to make sure that userInteraction is enabled again
[self enableTapGestureToCloseMenu:NO];
if (self.menuNeedsLayout)
{
[self updateMenuFrameAndTransformAccordingToOrientation];
// Handle different horizontal/vertical slideOffset during rotation
// On iOS below 8 we just close the menu, iOS8 handles rotation better so we support keepiong the menu open
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"8.0") && [self isMenuOpen])
{
Menu menu = (self.horizontalLocation > 0) ? MenuLeft : MenuRight;
[self openMenu:menu withDuration:0 andCompletion:nil];
}
self.menuNeedsLayout = NO;
}
}
Fixed out, that two lines must be in viewWillLayoutSubviews instead of setup method. And like #Ishika said, this is the problem of iOS-Slide-Menu.
self.enableShadow = YES;
self.enableSwipeGesture = YES;
This error occured to me with Xcode 10.2.1 and SideMenu 6.0.4. I ended up configuring the navigation controller programmatically, which solved the issue.
let sideMenuVc = UISideMenuNavigationController(rootViewController: <view controller>)
I add an imageView to my app's main window in applicationDidEnterBackground:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
UIImageView *splash = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"splashimage.png"]];
splash.frame = self.window.bounds;
[self.window addSubview:splash];
}
I expect that when I put the app into the background by pressing the device's home button, then viewing the task manager by double tapping the home button, I will see the splashimage.png displayed. But it seems that the screenshot that is taken when the app goes into the background does not include this overlay imageView. I thought that it would, since I added the imageView with no animation.
Why might this might be happening?
UPDATE: Even if I hide my window in applicationDidEnterBackground, I can still see the full window, unhidden, in the task manager after the app is put into the background state. The window only becomes hidden a moment after the app returns from the background, when I press the home button.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
//this does not work either!
self.window.hidden = YES;
}
UPDATE: I do understand, by the way, that applicationDidEnterBackground has 5 seconds to complete. I am now testing this with only
self.window.hidden = YES;
in applicationDidEnterBackground and nothing in applicationWillResignActive, and the window still is not hidden when the app goes into the background; only when it returns to the foreground. So this is telling me that there must be something else, somewhere in my app, that is not allowing this to happen in applicationDidEnterBackground. By the way, if I move the
self.window.hidden = YES;
to applicationWillResignActive, the window IS hidden when the app goes into the background. But I am trying to figure out what will inhibit an app from completing this single, simple, non-animated task in applicationDidEnterBackground. Any thoughts appreciated.
UPDATE: This particular issue has something to do with using a BannerViewController (iAd). I am using it in a UITabBarController. Not sure yet what exactly the issue is or if it is also related to its use within UITabBarController.
UPDATE: I think the issue is not related to the UITabBarController, but in general the BannerViewController (iAd). Now to understand why...
UPDATE: This line in BannerViewController.m is causing the issue:
[self.view addSubview:bannerView]
in this method:
- (void)viewDidLayoutSubviews
{
CGRect contentFrame = self.view.bounds, bannerFrame = CGRectZero;
ADBannerView *bannerView = [BannerViewManager sharedInstance].bannerView;
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0
NSString *contentSizeIdentifier;
// If configured to support iOS <6.0, then we need to set the currentContentSizeIdentifier in order to resize the banner properly.
// This continues to work on iOS 6.0, so we won't need to do anything further to resize the banner.
if (contentFrame.size.width < contentFrame.size.height) {
contentSizeIdentifier = ADBannerContentSizeIdentifierPortrait;
} else {
contentSizeIdentifier = ADBannerContentSizeIdentifierLandscape;
}
bannerFrame.size = [ADBannerView sizeFromBannerContentSizeIdentifier:contentSizeIdentifier];
#else
// If configured to support iOS >= 6.0 only, then we want to avoid currentContentSizeIdentifier as it is deprecated.
// Fortunately all we need to do is ask the banner for a size that fits into the layout area we are using.
// At this point in this method contentFrame=self.view.bounds, so we'll use that size for the layout.
bannerFrame.size = [_bannerView sizeThatFits:contentFrame.size];
#endif
if (bannerView.bannerLoaded) {
contentFrame.size.height -= bannerFrame.size.height;
bannerFrame.origin.y = contentFrame.size.height;
} else {
bannerFrame.origin.y = contentFrame.size.height;
}
_contentController.view.frame = contentFrame;
// We only want to modify the banner view itself if this view controller is actually visible to the user.
// This prevents us from modifying it while it is being displayed elsewhere.
if (self.isViewLoaded && (self.view.window != nil)) {
[self.view addSubview:bannerView];
bannerView.frame = bannerFrame;
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0
bannerView.currentContentSizeIdentifier = contentSizeIdentifier;
#endif
}
}
Not exactly sure why or what, if anything can be done to fix it if I still want to use BannerViewController.
UPDATE: from to the accepted answer below, here is what solved this problem for me (bannerVC is a reference to the BannerViewController).
- (void)applicationWillResignActive:(UIApplication *)application {
[bannerVC.view snapshotViewAfterScreenUpdates:YES];
}
UPDATE: I realize that this code really should be in applicationDidEnterBackground. But, it seems with AdBannerView that there is no way to stop the animations in time so that this can happen. So for now, in my understanding, applicationWillResignActive is all that is left, but it leaves the user with a 'less-than' experience. I would appreciate any suggestions on how to stop the AdBannerView animations so that the snapshot can be shown in applicationDidEnterBackground and not only when returning from the background.
UPDATE:
To replicate the issue:
Download iAdSuite
Open TabbedBanner example.
Add the code below to AppDelegate:
Run the app. (frames are messed up because it hasn't been updated but this example will still show the problem)
Tap the home button once to put the app into the background.
Double-tap the home button to see the app in the task manager.
If you have left the code in applicationWillResignActive commented and the code in applicationDidEnterBackground uncommented, you will not see a blue splash screen in the task manager. But if you have commented the code in applicationDidEnterBackground and uncomment the code in applicationWillResignActive, you should see the blue splash screen in the task manager. This is not desirable however. The issue is how to display the splash screen in the applicationDidEnterBackground method.
- (void)applicationWillResignActive:(UIApplication *)application {
//works - but undesirable
//[self addDummyView];
//[_tabBarController.view snapshotViewAfterScreenUpdates:YES];
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
//does not work - needs to work to show dummyview only when application actually goes into the background
[self addDummyView];
[_tabBarController.view snapshotViewAfterScreenUpdates:YES];
}
- (void)addDummyView {
UIView *topView = _tabBarController.view;
UIView *colorView = [[UIView alloc] initWithFrame:topView.frame];
[colorView setBackgroundColor:[UIColor blueColor]];
[topView addSubview:colorView];
[topView bringSubviewToFront:colorView];
}
When the applicationDidEnterBackground method returns, a snapshot of the current view of the app is taken to be used for the multitasking view. The reason that you are not seeing the update is because the snapshot is taken before the drawing cycle. Even if you call setNeedsDisplay the snapshot still is taken before.
To wait until the view updates (after you have added your subview) call this:
[self.view snapshotViewAfterScreenUpdates:YES];
with a value of YES. This forces your changes to render first. You can read more about handling stuff like this in apple docs here: https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/StrategiesforHandlingAppStateTransitions/StrategiesforHandlingAppStateTransitions.html#//apple_ref/doc/uid/TP40007072-CH8-SW27
You can get you answer here: Show splash screen when application enters background.
Keep your code in applicationWillResignActive.
Your code is fine. You should know that applicationDidEnterBackground is not called when you double tap the home button. It is called when you switch to another app or go to springboard.
So, you should press the home button only once, then double tap it and the screenshot of your app should have the image view. I replicated your use case in a test project and it works for me.
If it still does not work, you should see if the image of the image view is not nil. Oh, and please don't hide the app window.
I've spent quite a bit of time searching online and talking to other developers about this issue to no avail. The exact issue is described in this SO post (Focus on the UISearchBar but the keyboard not appear), although it's many years old.
I recently switched from using the deprecated UISearchDisplayController and UISearchBar in IB, and switched over to UISearchController via the code for iOS8.
The problem I'm getting however, is that focus is assigned correctly (you can tell because the cancel button animates to the right of the search bar after the view loads), however the keyboard does not show up.
Here's the code that I have.
.h
#property (nonatomic, strong) UISearchController *searchController;
.m
- (void)viewDidLoad {
[super viewDidLoad];
...
[self initializeSearchController];
....
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.searchController setActive:YES];
[self.searchController.searchBar becomeFirstResponder];
}
- (void)initializeSearchController {
self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
self.searchController.searchResultsUpdater = self;
self.searchController.dimsBackgroundDuringPresentation = NO;
self.searchController.delegate = self;
self.searchController.searchBar.delegate = self;
[self.searchController.searchBar sizeToFit];
[self.tableView setTableHeaderView:self.searchController.searchBar];
self.definesPresentationContext = YES;
}
The things I've tried so far.
I've tried calling becomeFirstResponder on a 0.2 second delay, as suggested in another SO post.
I've set a breakpoint in viewDidAppear, and verified that both self.searchController and self.searchController.searchBar are both valid objects, neither nil.
I've tried conforming to the UISearchControllerDelegate and using the following snippet of code
here:
- (void)didPresentSearchController:(UISearchController *)searchController {
//no matter what code I put in here to becomeFirstResponder, it doesn't
//matter because this is never called, despite setting the
//self.searchController.delegate = self AND
//self.searchController.searchBar.delegate = self.
}
I've created a new view from scratch in storyboards, and segued to that one instead, to make sure I didn't have some old searchBar remnant in my view. This did not work either.
I've only tested this on a real device (iPhone 6), and it's not a simulator issue of not showing the keyboard.
I'm out of ideas, and I've seen every question and answer related to this one the web. Nothing is working.
To clarify again what's going on, the searchBar correctly becomes the first responder, the cancel button to the right of it animates onscreen proving this, but the keyboard does not appear and the cursor does not blink in the searchBar.
Your code looks ok. What you are describing isn't normal behaviour.
The first thing you can do is to create a new project with just the UISearchController functionality and see how it goes. You can edit your question with it so we'll have a better view.
There's a good example on how to implement UISearchController here: Sample-UISearchController
Adding:
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.searchController.searchBar becomeFirstResponder];
}
to MasterViewController_TableResults.m gave the expected results and the keyboard popped up on launch on an iPad & iPhone with iOS 8.3.
You can go over that project and see what you did differently,
Edit:
Apparently if [self.searchController setActive:YES] is called before becomeFirstResponder the keyboard won't show. I wonder if that's a bug or not.
Had the same annoying issue.
You would think that by setting the SearchController as active would both present the the search controller and the keyboard. Unfortunately, it only does the first part.
My solution
in viewDidAppear make the Search Controller active:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
resultSearchController.active = true
}
once it is active, in didPresentSearchController make as first responder
func didPresentSearchController(searchController: UISearchController) {
searchController.searchBar.becomeFirstResponder()
}
Swift 3.0 (iOS 10) working solution:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
searchController.isActive = true
DispatchQueue.main.async { [unowned self] in
self.searchController.searchBar.becomeFirstResponder()
}
}
On iOS 9 I've found its sufficient to delay becomeFirstResponder() to the next run loop:
func focusSearchField() {
searchController?.active = true
// skipping to the next run loop is required, otherwise the keyboard does not appear
dispatch_async(dispatch_get_main_queue(), { [weak self] in
self?.searchController?.searchBar.becomeFirstResponder()
})
}
Working Solution:-
Don't use [self.searchController setActive:YES] before becomeFirstResponder.
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
// [self.searchController setActive:YES];
[self.searchController.searchBar becomeFirstResponder];
});
});
}
In iOS 10, I had to run the code in delegate method on main thread. First I set the active to YES in viewDidAppear,
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[self.searchController setActive:YES];
}
and then in the delegate method:
- (void)didPresentSearchController:(UISearchController *)searchController
{
dispatch_async(dispatch_get_main_queue(), ^{
[searchController.searchBar becomeFirstResponder];
});
}
The solution that will work is as follows :
1.Override ViewDidLayoutSubviews in the view controller in which you are showing UISearchController
2.Override ViewDidLayoutSubviews and inside it make search bar first responder.
Tested it on iOS > 9.0
Caution : Put a null check before making it First responder as follows
if((searchController != null)&&(searchController.SearchBar != null))
searchController.SearchBar.BecomeFirstResponder();
This is because ViewDidLayoutSubviews also gets called when cancel button is pressed.
This worked for me in Xamarin.
I had trouble with an UISearchBar not displaying the keyboard when doing
[searchBar becomeFirstResponder];
By searching on the net, i found this thread on the Apple developer website
that helped me to discover that the keyboard won't open if you don't have a keyWindow.
The application i work on do something like this :
Window A (KeyWindow)
do some things
open Window B (KeyWindow)
do some things
close Window B (resign KeyWindow)
I just had to do
[[[[UIApplication sharedApplication] windows] firstObject] makeKeyWindow];
after the resigning of window B and no more trouble with the keyboard.
This might also be related to Simulator Settings. Just disable Hardware -> Keyboard -> "Connect Hardware Keyboard" .
For further details: UISearchBar not showing keyboard when tapped
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.searchController setActive:YES];
}
//and then in the delegate method:
- (void)didPresentSearchController:(UISearchController *)searchController
{
dispatch_async(dispatch_get_main_queue(), ^{
[searchController.searchBar becomeFirstResponder];
});
}
//The above works for me in addition to this I had to add:
-(void)viewWillDisappear:(BOOL)animated {
[searchController setActive:NO];
}
I've developed a game for iPhone starting from iOS 6.0.
It works fine on my 5S running on iOS 7.1 but when testing it on my old 3GS with iOS 6.1.3, it crash with a "EXC_BAD_ACCESS code:0" when trying to remove some custom UIView from superview.
I logged my custom view and it's superview (that are not nil), but nothing to do it work !
Also tried logging my views and superviews with lldb command and it confirms they are not nil.
Any idea ?? Can't understand how it could work on iOS7 and not on iOS6 !
Here the code when I get the error :
- (void)didMoveToParentViewController:(UIViewController *)parent{
if(parent == nil){
for (LevelNumberView *button in self.levelButtons) {
if(self.view && [self.view.subviews containsObject:button]){
NSLog(#"-- button:%#", button);
[button removeFromSuperview];
}
}
self.levelButtons = nil;
[self.titleView removeFromSuperview];
self.titleView = nil;
[self.view removeFromSuperview];
self.delegate = nil;
}
}
You should be able to call removeFromSuperview on any valid UIView (whether it has a superview or not) so that should not cause an error. My guess would be one of the pointers (that is not nil) is pointing to a released object.
I fix the same issue using check the superview before remove it :
if(self.loadingView.superview){
[self.loadingView removeFromSuperview];
}
I am using setNeedsDisplay on my GUI, but there update is sometimes not done. I am using UIPageControllView, each page has UIScrollView with UIView inside.
I have the following pipeline:
1) application comes from background - called applicationWillEnterForeground
2) start data download from server
2.1) after data download is finished, trigger selector
3) use dispatch_async with dispatch_get_main_queue() to fill labels, images etc. with new data
3.1) call setNeedsDisplay on view (also tried on scroll view and page controller)
Problem is, that step 3.1 is called, but changes apper only from time to time. If I swap pages, the refresh is done and I can see new data (so download works correctly). But without manual page turn, there is no update.
Any help ?
Edit: code from step 3 and 3.1 (removed _needRefresh variables pointed in comments)
-(void)FillData {
dispatch_async(dispatch_get_main_queue(), ^{
NSString *stateID = [DataManager ConvertStateToStringFromID:_activeCity.actual_weather.state];
if ([_activeCity.actual_weather.is_night boolValue] == YES)
{
self.contentBgImage.image = [UIImage imageNamed:[NSString stringWithFormat:#"bg_%#_noc", [_bgs objectForKey:stateID]]];
if (_isNight == NO)
{
_bgTransparencyInited = NO;
}
_isNight = YES;
}
else
{
self.contentBgImage.image = [UIImage imageNamed:[NSString stringWithFormat:#"bg_%#", [_bgs objectForKey:stateID]]];
if (_isNight == YES)
{
_bgTransparencyInited = NO;
}
_isNight = NO;
}
[self.contentBgImage setNeedsDisplay]; //refresh background image
[self CreateBackgroundTransparency]; //create transparent background if colors changed - only from time to time
self.contentView.parentController = self;
[self.contentView FillData]; //Fill UIView with data - set labels texts to new ones
//_needRefresh is set to YES after application comes from background
[self.contentView setNeedsDisplay]; //This do nothing ?
[_grad display]; //refresh gradient
});
}
And here is selector called after data download (in MainViewController)
-(void)FinishDownload:(NSNotification *)notification
{
dispatch_async(dispatch_get_main_queue(), ^{
[_activeViewController FillData]; //call method shown before
//try call some more refresh - also useless
[self.pageControl setNeedsDisplay];
//[self reloadInputViews];
[self.view setNeedsDisplay];
});
}
In AppDelegate I have this for application comes from background:
-(void)applicationWillEnterForeground:(UIApplication *)application
{
MainViewController *main = (MainViewController *)[(SWRevealViewController *)self.window.rootViewController frontViewController];
[main UpdateData];
}
In MainViewController
-(void)UpdateData
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(FinishForecastDownload:) name:#"FinishDownload" object:nil]; //create selector
[[DataManager SharedManager] DownloadForecastDataWithAfterSelector:#"FinishDownload"]; //trigger download
}
try this:
[self.view performSelectorOnMainThread:#selector(setNeedsLayout) withObject:nil waitUntilDone:NO];
or check this link:
http://blackpixel.com/blog/2013/11/performselectoronmainthread-vs-dispatch-async.html
setNeedsDisplay triggers drawRect: and is used to "redraw the pixels" of the view , not to configure the view or its subviews.
You could override drawRect: and modify your labels, etc. there but that's not what it is made for and neither setNeedsLayout/layoutSubviews is.
You should create your own updateUI method where you use your fresh data to update the UI and not rely on specialized system calls meant for redrawing pixels (setNeedsDisplay) or adjusting subviews' frames (drawRect:).
You should set all your label.text's, imageView.image's, etc in the updateUI method. Also it is a good idea to try to only set those values through this method and not directly from any method.
None of proposed solutions worked. So at the end, I have simply remove currently showed screen from UIPageControllView and add this screen again. Something like changing the page there and back again programatically.
Its a bit slower, but works fine.