I have several view controllers with one or multiple scrollviews. Although I have explicitly set the scrollsToTop flags in view controllers with more than one scroll view, some scroll views refuse to scroll up when I tap the status bar.
After pushing another view controller and popping it the gesture sometimes works in the view it previously hasn't.
It's very confusing and I just don't know what the problem is. How can this issue effectively be debugged? Is there a global (private) notification for the status bar tap so I could scroll the views manually?
I have used code like the following to debug this scenario, before:
- (void)findMisbehavingScrollViews
{
UIView *view = [[UIApplication sharedApplication] keyWindow];
[self findMisbehavingScrollViewsIn:view];
}
- (void)findMisbehavingScrollViewsIn:(UIView *)view
{
if ([view isKindOfClass:[UIScrollView class]])
{
NSLog(#"Found UIScrollView: %#", view);
if ([(UIScrollView *)view scrollsToTop])
{
NSLog(#"scrollsToTop = YES!");
}
}
for (UIView *subview in [view subviews])
{
[self findMisbehavingScrollViewsIn:subview];
}
}
Depending on how many UIScrollViews you find, you can modify that code to help debug your particular situation.
Some ideas:
Change the background colors of the various scrollviews to identify them on screen.
Print the view hierarchy of those scrollviews to identify all of their superviews.
Ideally, you should only find a single UIScrollView in the window hierarchy that has scrollsToTop set to YES.
I changed the accepted answers into swift for convenience:
Swift 5
func findMisbehavingScrollViews() {
let topView = UIApplication.shared.windows.first { $0.isKeyWindow }
findMisbehavingScrollViewsIn(topView!)
}
func findMisbehavingScrollViewsIn(_ topView: UIView) {
if let topView = topView as? UIScrollView {
if topView.scrollsToTop {
print("Found UIScrollView: \(topView)")
}
for nextView in topView.subviews {
findMisbehavingScrollViewsIn(nextView)
}
}
}
Related
When the UIDocumentInteractionController is dismissed, the presenting view controller's views are removed, including elements from the UINavigationController.
The UIDocumentInteractionController dismisses and the presenting view controller's views are removed, leaving a plain white/gray box where the presenting view controller formerly existed. The app no longer responds to any touch events after this point.
This Occurs on iPad Simulator (iOS 7.0) and iPad 3 (Wifi) running iOS 7 for Quick Look Pdf Reader.
Does not matter whether the application was compiled against the iOS 6.1 or iOS 7 SDK
Please let me know your suggestions.
I have this same problem when presenting a UIDocumentInteractionController from a view controller presented as a modal form sheet on iPad in iOS 7 (worked fine in iOS 6).
It looks like that during the transition from the document interaction controller back to the presenting view controller, the presenting view controller's view is wrapped in a temporary UITransitionView, and then that transition view is being removed from the view hierarchy after the transition is complete, along with the presenting view controller's view, leaving just UIDropShadowView that is the backing view of the modal form sheet visible (the gray box).
I worked around the problem by keeping a reference to my presenting view controller's root view (the one just before the drop shadow view in the hierarchy) when the document interaction controller preview will begin, and restoring that view to the hierarchy when the document interaction controller preview has ended.
Here's sample code:
- (void)documentInteractionControllerWillBeginPreview:(__unused UIDocumentInteractionController *)controller {
if (UIUserInterfaceIdiomPad == UI_USER_INTERFACE_IDIOM()) {
// work around iOS 7 bug on ipad
self.parentView = [[[self.view superview] superview] superview];
self.containerView = [self.parentView superview];
if (![[self.containerView superview] isKindOfClass: [UIWindow class]]) {
// our assumption about the view hierarchy is broken, abort
self.containerView = nil;
self.parentView = nil;
}
}
}
- (void)documentInteractionControllerDidEndPreview:(__unused UIDocumentInteractionController *)controller {
if (UIUserInterfaceIdiomPad == UI_USER_INTERFACE_IDIOM()) {
if (!self.view.window && self.containerView) {
assert(self.parentView);
CGRect frame = self.parentView.frame;
frame.origin = CGPointZero;
self.parentView.frame = frame;
[self.containerView addSubview: self.parentView];
self.containerView = nil;
self.parentView = nil;
}
}
}
I found Michael Kuntscher answer to be right on target. A slight modification is necessary if the UIDocumentInteractionController is presented from a popover.
There is a slight dependence on the view hierarchy that can be eliminated by iterating over the parent views until one with a UIWindow superview is found. In addition, I found that when a document interaction controller is presented from within a popover it is slightly different views that need to be stored as the parentView and containerView (specifically we want to find a containerView such that its superview is a UIPopoverView). The following snippet is a re-worked version of Michael's answer to incorporate these changes (note that UIPopoverView is a private class, so we use string representations of the class rather than making a direct reference to each class):
- (void)documentInteractionControllerWillBeginPreview:(UIDocumentInteractionController *)controller {
/* iOS 7 DIC bug workaround */
if (UIUserInterfaceIdiomPad == UI_USER_INTERFACE_IDIOM()) {
UIView *a_view = self.view;
self.dicParentView = nil;
self.dicContainerView = nil;
while (a_view != nil) {
UIView *super_super_view = [[a_view superview] superview];
NSString *str_class = NSStringFromClass([super_super_view class]);
if ([str_class isEqualToString:#"UIWindow"] ||
[str_class hasSuffix:#"PopoverView"]) {
self.dicParentView = a_view;
self.dicContainerView = [a_view superview];
break;
}
a_view = [a_view superview];
}
if (self.dicParentView == nil) {
NSLog(#"Could not appropriate superview, unable to workaround DIC bug");
}
}
/* end work around */
}
- (void)documentInteractionControllerDidEndPreview:(__unused UIDocumentInteractionController *)controller {
/* iOS 7 DIC bug workaround */
if (UIUserInterfaceIdiomPad == UI_USER_INTERFACE_IDIOM()) {
if ((self.view.window == nil) &&
(self.dicContainerView != nil) &&
(self.dicParentView != nil)) {
NSLog(#"Restoring view for DIC presenter in the view hierarchy");
CGRect frame = self.dicParentView.frame;
frame.origin = CGPointZero;
self.dicParentView.frame = frame;
[self.dicContainerView addSubview: self.dicParentView];
self.dicContainerView = nil;
self.dicParentView = nil;
}
}
/* end work around */
}
I had the exact same problem with the app stalling on a grey form sheet modal view that had lost all of its content after a UIDocumentInteractionController had been presented and dismissed. The two solutions here are great but I simplified them to cater for my particular case, which was a UINavigationController inside a form sheet modal that can present a PDF in the UIDocumentInteractionController, which I wanted to be full screen modal instead of pushed in the navigation controller because the form sheet area is too small for the PDF to be easily readable.
I implemented two UIDocumentInteractionControllerDelegate methods. Assume the following:
self.navController is a reference to the UINavigationController that is presented inside the form sheet modal.
there is a member variable declared in the UIViewController subclass #property (nonatomic, strong) UIView* docInteractionControllerWorkaroundSuperview;
SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO is a #define for ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
Firstly:
-(UIViewController*)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController*)controller
{
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"7.0") && self.navigationController.modalPresentationStyle == UIModalPresentationFormSheet)
{
self.docInteractionControllerWorkaroundSuperview = [self.navigationController.view superview];
}
return self.navigationController.visibleViewController;
}
then:
- (void)documentInteractionControllerDidEndPreview:(UIDocumentInteractionController *)controller
{
if (self.docInteractionControllerWorkaroundSuperview != nil)
{
NSLog(#"Workaround iOS 7 document interaction bug... resetting nav controller view into modal");
//reset the nav controller view from whence it came.
self.navigationController.view.frame = CGRectMake(0.0, 0.0, self.navigationController.view.frame.size.width, self.navigationController.view.frame.size.height);
[self.docInteractionControllerWorkaroundSuperview addSubview:self.navigationController.view];
self.docInteractionControllerWorkaroundSuperview = nil;
}
}
i.e. when presenting the UIDocumentInteractionController, look at the superview of the navigation controller's view. It works out to be a UIDropShadowView which I assume is the partially transparent grey/black background to the form sheet modal that dims out the view behind that presented the modal.
When the PDF has been dismissed, in documentInteractionControllerDidEndPreview the superview of the navigation controller's view is now a UITransistionView. But then shortly after that (when the transition has completed), it gets set to nil. Somehow it got detached (or didn't get re-attached) from the UIDropShadowView. By keeping a reference to view this when presenting the UIDocumentInteractionController, we can reattach it manually and everything works fine. Then be sure to nil out the reference to make sure we don't accidentally retain it.
This method does not affect the behaviour on iOS 6 or any previous iOS versions.
I have category for UIScrollView for customization
I need get UIScrollView.
I try
UIScrollView *scroll = nil;
for (UIView* subview in self.tableView.subviews) {
NSLog(#"%i", self.tableView.subviews.count);
if ([subview isKindOfClass:[UIScrollView class]]) {
scroll = (UIScrollView *)subview;
//some code
}
}
But it doesn't work. How I can get ScrollView? Thanks
UITableView is a subclass of UIScrollView, not a subview (or vice-versa as your code tries), so you should just use the table view directly.
UIScrollView is not subview but UIScrollView is superclass of UITableView you can call method or get variable of UIScrollView by through UIScrollView, If you want to get UIScrollView you can use by this
var scroll = self.tableView as UIScrollView
//some code
Or you want to compare you can use
if scrollView == self.tableView {
//some code
}
I am adding UIInterfaceOrientation changes to the app. My app is not a tab-based or navigation based. I am adding subviews on top of other.
I went through the document why interface orientation changes not work?
http://developer.apple.com/library/ios/#qa/qa1688/_index.html
I found that the main window controls the interface orientation changes.
But now as my app is not tab-based or navigation-based, is there still any way to achieve it?
P.S. : In my app only launch screens are working properly for interface orientation changes and not the added subviews.
Please help
Thanks in advance.
Use following method to resize subview. OR use autoresizingMask
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
if(toInterfaceOrientation == UIInterfaceOrientationPortrait){
viewInformationContainer.frame = CGRectMake(0, 61, 320, 403);// here you can change resize or subviews
}
else if (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight || toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
viewInformationContainer.frame = CGRectMake(0, 0, 480, 227);//here you can change resize or subviews
}
}
Hope, this will help you...
Reading the comments posted above and the problem that you are facing what I can say is that You have added a hierarchy of subviews. Consider the following Case
1) I add a view controller (VC) , Navigation Controller , Tab bar controller directly to the window.. the subsequent views will get notify about any orientation changes properly.
2) I add a Parent view Controller to the window and later add views of some other view controllers to my parent view controller, in this particular case you will get the orientation change delegate methods being called properly in the Parent VC but not in the subsequent view controllers whose view you have added to your Parent VC
I once added a Base view controller to my application window where base controller had a ScrollView , I later added subviews of my four view controllers to scrollview.
(void)viewDidLoad {
DNAppDelegate *appDelegate = (DNAppDelegate*)[UIApplication sharedApplication].delegate;
CGRect viewCustormFrame = appDelegate.viewCustomTabBar.frame;
viewCustormFrame.origin.y = self.view.frame.size.height-35;
if(UIInterfaceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation)) {
viewCustormFrame.size.width = self.view.frame.size.width;
}
else {
viewCustormFrame.size.width = self.view.frame.size.width;
}
[self.view addSubview:appDelegate.viewCustomTabBar];
appDelegate.viewCustomTabBar.frame = viewCustormFrame;
[srclViewMainContent addSubview:appDelegate.homeController.view];
[srclViewMainContent addSubview:appDelegate.newsListController.view];
[srclViewMainContent addSubview:appDelegate.columnController.view];
[srclViewMainContent addSubview:appDelegate.whatsOnController.view];
currentVisisbleController = appDelegate.homeController;
[self resetAllSizes];
[super viewDidLoad];
}
In above case though i got all orientation delegate methods being called in base VC but not in the other Four VC.. so i had to tweak something like
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
DNAppDelegate *appDelegate = (DNAppDelegate*)[UIApplication sharedApplication].delegate;
[appDelegate.homeController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
[appDelegate.newsListController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
[appDelegate.columnController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
[appDelegate.whatsOnController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
return (interfaceOrientation == UIInterfaceOrientationLandscapeLeft || interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
DNAppDelegate *appDelegate = (DNAppDelegate*)[UIApplication sharedApplication].delegate;
currentPageOffset = srclViewMainContent.contentOffset.x/srclViewMainContent.frame.size.width;
[appDelegate.homeController willRotateToInterfaceOrientation:toInterfaceOrientation duration:(NSTimeInterval)duration];
[appDelegate.newsListController willRotateToInterfaceOrientation:toInterfaceOrientation duration:(NSTimeInterval)duration];
[appDelegate.columnController willRotateToInterfaceOrientation:toInterfaceOrientation duration:(NSTimeInterval)duration];
[appDelegate.whatsOnController willRotateToInterfaceOrientation:toInterfaceOrientation duration:(NSTimeInterval)duration];
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
DNAppDelegate *appDelegate = (DNAppDelegate*)[UIApplication sharedApplication].delegate;
[self resetAllSizes];
CGRect visibleRect = CGRectMake(currentPageOffset*srclViewMainContent.frame.size.width, 0, srclViewMainContent.frame.size.width, srclViewMainContent.frame.size.height);
[srclViewMainContent scrollRectToVisible:visibleRect animated:NO];
[appDelegate.homeController didRotateFromInterfaceOrientation:fromInterfaceOrientation];
[appDelegate.newsListController didRotateFromInterfaceOrientation:fromInterfaceOrientation];
[appDelegate.columnController didRotateFromInterfaceOrientation:fromInterfaceOrientation];
[appDelegate.whatsOnController didRotateFromInterfaceOrientation:fromInterfaceOrientation];
}
i.e call these delegate methods on respective VCs from the Base VC. I had to do this because my application was completely build and later I had to change the architecture to allow scrolling among various views, But I had already implemented these views by taking four View controllers.
In a nutshell any VC (also Navigation Controller, tab Bar controller, split View controller)added directly to window will propagate these Orientation change delegate methods to subsequent VCs but a View controller will not pass these delegate methods to VCs coming down the lane
I hope this gives you some insight. Just in case if you come across any good solution do post in as an answer and share with us all.
I have a bunch of subViews in my ViewController.
In the last layer I have a UIView, and from this view I want to call superview and go up until I find the UIView that belongs to my ViewController.
Is it possible to find out whether a UIView belongs to a ViewController or not?
UIView *someView = self.superView;
while (true)
{
if (someView BELONGS TO VIEWCONTROLLER)
{
// Now we know this view belongs to a VIewController
break;
}
someView = someView.superView;
}
If you want to find out if a certain view is in the hierarchy managed by a view controller and you have a pointer to the view controller:
BOOL belongsToController = [aView isDescendantOfView:viewController.view];
Alternatively, if you want to find out if a certain view is the root of the hierarchy managed by the view controller but you don't have a pointer to the view controller, you can traverse the responder chain. According to the UIResponder's nextResponder documentation:
UIView implements this method by returning the UIViewController object that manages it (if it has one) or its superview (if it doesn’t)
Therefore, if the next responder of a certain view is a UIViewController, that view must be the view associated with the view controller.
if ([[aView nextResponder] isKindOfClass:[UIViewController class]]) {
// aView is the root of the view hierarchy managed by the view controller
}
Vlad's and albertamg's approaches are correct as well. However you can also traverse the responder chain
for (UIView* next = [self superview]; next; next = next.superview) {
UIResponder* nextResponder = [next nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]]) {
UIViewController *theControllerThatYouWANT = (UIViewController*)nextResponder;
}
}
try going up in the hierarchy of views and check if current view object is the same as your controller's view
Code would be something like this: (wrote in textEdit, don't have dev tools here, sorry if any mistakes)
-(BOOL)view:(UIView *)aView belongsToController:(UIViewController *)viewController {
BOOL belongsToController = NO;
UIView *someView = [aView superView];
while (someView != nil) {
if (viewController.view == someView) {
belongsToController = YES;
}
someView = [someView superView];
}
return belongsToController;
}
just tested it and it works for me. I hope it was helpfull.
Vlad
I have a UIView subclass -
#interface DatePickerPopup : UIView
UIToolbar *toolbar;
UIDatePicker *datePicker;
#end
#implementation
- (id)initWithFrame:(CGRect)frame
{
NSArray *xib =
[[NSBundle mainBundle]
loadNibNamed:#"DatePickerPopup"
owner:self
options:nil];
self = [xib objectAtIndex:0];
if (self) {
}
return self;
}
#end
and the nib looks like -
In my UIViewController containing the DatePickerPopup (datePopup):
- (void)viewDidLoad
{
datePopup = [[DatePickerPopup alloc] initWithRect:CGRectZero];
CGRect newFrame = datePopup.frame;
newFrame.y = 200.0f; //lets say this aligns it to the bottom in portrait
datePopup.frame = newFrame;
// Normally happens when accessory button pressed but for brevity...
[self.view.superview addSubview:datePopup];
}
- (void)willAnimateRotationToInterfaceOrientation:
(UIInterfaceOrientation)toInterfaceOrientation
duration:(NSTimeInterval)duration
{
CGRect screen = [[UIScreen mainScreen] bounds];
if (toInterfaceOrientation == UIInterfaceOrientationPortrait ||
toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
{
self.datePopup.frame =
CGRectMake(0.0f, newHeightPortrait, screen.size.width, 260.0f);
}
else
{
self.datePopup.frame =
CGRectMake(0.0f, newHeightLandscape, screen.size.width, 260.0f);
}
}
However, this gets stretched out for some reason when the orientation changes the view gets stretched to the height of the screen bounds - the navigation bar...
after viewDidLoad
after willAutorotate...
Since your view controller appears to be managed by a navigation controller, calling [self.view.superview addSubview:datePopup]; adds your popup as a subview of a UIViewControllerWrapperView, which is one of the private classes UIKit uses to implement the functionality of UINavigationController. Messing with UIKit's private view hierarchy is always risky. In this case, based on the behavior you're seeing, it seems likely that UIKit expects any subview of UIViewControllerWrapperView to be a view controller's view, so it resizes your popup accordingly.
I think the safest way to resolve this is to have your view controller's view be a wrapper that contains your tableView and, when necessary, your popup view. Unfortunately using a wrapper view means that the view controller can't be a UITableViewController. You'll have to change the superclass to UIViewController, set a custom tableView property, and manually adopt the UITableViewDataSource and UITableViewDelegate protocols.
Note: You might be tempted to add your popover as a subview of your window, but I'm not recommending that because UIWindow only autorotates its topmost subview corresponding to a view controller. This means that if you add your popover to your window, it won't autorotate.
EDIT: BTW, by reassigning self = [xib objectAtIndex:0]; in initWithFrame:, you're leaking the object that was originally alloc'd. If you're going to reassign self in this way, you should release the existing object first.
Add the
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
method in the viewController class and return YES. If this method returns YES, only then will the device support landscape orientation. Try out this extra code and see if it helps...
You can set the frame size for landscape in this method itself instead of the current method. PS: I just saw you've used a UIView instead of controller...you might want to change to controller.