I am able to scroll all the way down with the code below:
- (void)viewDidLayoutSubviews {
dispatch_async (dispatch_get_main_queue(), ^ {
CGFloat scrollViewHeight = 0.0f;
for (UIView* view in self.scrollView.subviews) {
scrollViewHeight += view.frame.size.height;
}
[self.scrollView setContentSize:(CGSizeMake(320, scrollViewHeight))];
});
}
but when I scroll, the contentView (in gray) is short and everything below it does not work:
EDIT:
EDIT Two things might be wrong with the layout. (1) The single subview of the scroll view (gray) needs to be large enough to encompass it's subviews, and (2) as the OP implies, so must the scrollView contentSize.
UIView *grayView = // get a pointer to that grayView
CGFloat maxContentY = 0.0;
for (UIView* view in grayView.subviews) {
maxContentY = MAX(maxContentY, CGRectGetMaxY(view.frame));
}
grayView.frame = CGRectMake(0,0, 320,maxContentY);
If that's the only subview of the scrollView, then the contentSize is easy:
[self.scrollView setContentSize:grayView.frame.size];
If there are other subviews, beside the gray one, then my original answer holds also, size the grayView as above and figure the max extent of the other subviews:
We typically aim to make the contentSize.height large enough to encompass the bottom edge of the bottom-most subview.
That's different calculation than the sum of the heights of the subviews, as the OP has it. (Consider a single subview at offset y == 1000 and height == 2. We want a height of 1002, not 2), so...
- (void)viewDidLayoutSubviews {
dispatch_async (dispatch_get_main_queue(), ^ {
CGFloat maxY = 0.0f;
for (UIView* view in self.scrollView.subviews) {
maxY = MAX(maxY, CGRectGetMaxY(view.frame));
}
[self.scrollView setContentSize:(CGSizeMake(320, maxY))];
});
}
The async invocation is harmless but hopefully unnecessary. Either the loop runs in trivial time, or we've added too many subviews.
try this one
- (void)viewDidLayoutSubviews {
dispatch_async (dispatch_get_main_queue(), ^ {
CGRect contentRect = CGRectZero;
for (UIView *view in self.scrollView.subviews)
contentRect = CGRectUnion(contentRect, view.frame);
[self.scrollView setContentSize:contentRect.size];
});
}
Related
I have a view, embedded into a container that it itself inside a UIScrollView.
This view contains a text aligned to its edges as well as a button, again aligned to edges.
When launching the controller view, inside viewDidLayoutSubviews, I get the actual real height of the screen, then I call the view to be resized this way :
- (void)viewDidLayoutSubviews {
[self log:#"ViewDidLayoutSubviews"];
//::.. change scroll height according to main view ..::
float mainHeight = _subMainView.frame.size.height;
float scrollHeight = mainHeight - _scrollView.frame.origin.y;
_scrollView.frame = CGRectMake(_scrollView.frame.origin.x, _scrollView.frame.origin.y, _scrollView.frame.size.width, scrollHeight);
//::.. find buttons in child ..::
UIViewController *child = [self.childViewControllers lastObject];
//::.. Get only block views ::..
NSArray *blockViews = [myTools getAllSubviewsFromView:child.view ofClass:[UIView class] byTagFrom:1000 toTag:1010];
[self resizeViews:blockViews intoFrame:_scrollView.frame withVerticalPadding:16.0f andTopMargin:(31.0f+16.0f) andBottomMargin:0.0f andLeftMargin:16.0f andRightMargin:16.0f];
NSArray *buttons = [myTools getAllSubviewsFromView:child.view ofClass:[UIButton class]];
[self resizeViews:buttons intoFrame:_scrollView.frame withVerticalPadding:16.0f andTopMargin:(31.0f+16.0f) andBottomMargin:0.0f andLeftMargin:16.0f andRightMargin:16.0f];
for (UIButton *button in buttons) {
[button invalidateIntrinsicContentSize];
}
}
And here is the resize code in question :
+ (void)resizeViews:(NSArray *)views intoFrame:(CGRect)parentFrame withVerticalPadding:(float)verticalPadding andTopMargin:(float)topMargin andBottomMargin:(float)bottomMargin andLeftMargin:(float)leftMargin andRightMargin:(float)rightMargin {
float blockHeight = ((parentFrame.size.height - topMargin - bottomMargin) / views.count);
blockHeight -= verticalPadding;
float origin = topMargin;
for (UIView *aView in views) {
[aView layoutIfNeeded];
for (NSLayoutConstraint *constraint in aView.constraints) {
if (constraint.firstAttribute == NSLayoutAttributeHeight) {
constraint.constant = blockHeight;
break;
}
}
origin += blockHeight + verticalPadding;
[aView setNeedsUpdateConstraints];
[aView layoutIfNeeded];
}
}
The visual result is good because both views and there content (the buttons) are correctly resized (the button is aligned centered vertically and horizontally so it is easy to show the good alignment).
However, the first button has is whole area touchable where the second one, under it, can be touched only on its upper height, before its middle...
Can't understand how it is possible that a button, who's size is good, become untouchable on its whole part ?
Any help ? :)
Thanks a lot.
I've created a RootViewController / RootView that:
Handles the content layout for the app
Exposes and interface for performing application level behaviors, like presenting the "hamburger" menu or overlay views with CAKeyframe animations.
This is in accordance with good practice.
The Problem:
When the main content view presents a form, there's a utility to animate the frame of that view, when a field is selected that would otherwise be obscured by the keyboard. This has been working fine all the way up until iOS 8.0.2
On iOS 8.0.2 the frame for the form will no longer animate if you set a negative value for origin.y. Instead of going from the current origin.y to the required origin.y it jerks down by the amount it was supposed to move, then animates back to 0.
If I present the form outside of the RootVC it works correctly.
What I've tried:
Checked that RootView is not doing anything in layout subviews to prevent the animation. (In iOS8.0 it was. I removed this and problem was solved. Only to return in iOS8.0.2)
Checked the BeginFromCurrentState flags.
Instead of animating the form view, animate [UIScreen mainScreen].keyWindow. Works but causes some other side effects that I don't want.
Question:
What has changed with animation of UIViewControllers that are contained in another view in iOS8.0.2. It seems to be something very fundamental.
The code that animates frame to move input fields out the keyboard's way:
Looks something like this:
- (void)scrollToAccommodateField:(UIView *)view
{
UIView *rootView = [UIApplication sharedApplication].keyWindow.rootViewController.view;
CGPoint position = [view convertPoint:view.bounds.origin toView:rootView];
CGFloat y = position.y;
CGFloat scrollAmount = 0;
CGFloat margin = 25;
CGSize screenSize = [self screenSizeWithOrientation:[UIApplication sharedApplication].statusBarOrientation];
CGSize accessorySize = CGSizeMake(_view.width, 44);
CGFloat maxVisibleY = screenSize.height - [self keyboardSize].height - accessorySize.height - margin;
if (y > maxVisibleY)
{
scrollAmount = maxVisibleY - y;
CGFloat scrollDelta = scrollAmount - _currentScrollAmount;
_currentScrollAmount = scrollAmount;
[self scrollByAmount:scrollDelta];
}
else
{
if (_currentScrollAmount != 0)
{
_currentScrollAmount = 0;
[UIView transitionWithView:_view duration:0.30
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseOut animations:^
{
_view.frame = [_view bounds];
} completion:nil];
}
}
}
Update:
I've since installed TPKeyboardAvoiding pod and its working very well. . leaving this open, in case its of interest to others.
I'm working with Mixare AR SDK for iOS and I need to solve some bugs that happends, one of them is show the information of a POI when the POI's view is tapped.
Prelude:
Mixare has an overlay UIView within MarkerView views are placed, MarkerView views are moving around the screen to geolocate the POIs and each one has two subviews, an UIImageView and an UILabel.
Issue:
Now, for example, there are 3 visible POIs in the screen, so there are 3 MarkerView as overlay subviews. If you touch anywhere in the overlay, a info view associated to a random POI of which are visible is showed.
Desired:
I want that the associated POI's info is shown only when the user tapped a MarkerView
Let's work. I've see that MarkerView inherits from UIView and implements hitTest:withEvent
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
viewTouched = (MarkerView*)[super hitTest:point withEvent:event];
return self;
}
I've put a breakpoint and hitTest is called once for each visible MarkerView but loadedView always is null so I can't work with it, so I've tried to check if the hit point is inside the MarkerView frame implementing pointInside:withEvent: by this way
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
NSLog(#"ClassName: %#", [[self class] description]);
NSLog(#"Point Inside: %f, %f", point.x, point.y);
NSLog(#"Frame x: %f y: %f widht:%f height:%f", self.frame.origin.x, self.frame.origin.y, self.frame.size.width, self.frame.size.height);
if (CGRectContainsPoint(self.frame, point))
return YES;
else
return NO;
return YES;
}
But this function always returns NO, even when I touch the MarkerView. When I check the log I saw that X and Y point values has negative values sometimes and width and height of the view are very small, 0.00022 or similar instead of 100 x 150 that I set the MarkerView frame on its initialization.
Here you are a extract of my log in which you can see the class name, the point and the MarkerView frame values.
ClassName: MarkerView
2011-12-29 13:20:32.679 paisromanico[2996:707] Point Inside: 105.224899, 49.049023
2011-12-29 13:20:32.683 paisromanico[2996:707] Frame x: 187.568573 y: 245.735138 widht:0.021862 height:0.016427
I'm very lost with this issue so any help will be welcome. Thanks in advance for any help provided and I'm sorry about this brick :(
Edit:
At last I've found that the problem is not in hitTest:withEvent: or pointInside:withEvent, problem is with CGTransform that applies to the MarkerView for scaling based on distande and rotating the view, if I comment any code related to this, the Mixare AR SDK works fine, I mean, info view is shown correctly if you touch a marker and doesn't do anything if any other place in the screen is touched.
So, by the moment, I've not solved the problem but I applied a patch removing the CGTransform related code in AugmentedViewController.m class - (void)updateLocations:(NSTimer *)timer function
- (void)updateLocations:(NSTimer *)timer {
//update locations!
if (!ar_coordinateViews || ar_coordinateViews.count == 0) {
return;
}
int index = 0;
NSMutableArray * radarPointValues= [[NSMutableArray alloc]initWithCapacity:[ar_coordinates count]];
for (PoiItem *item in ar_coordinates) {
MarkerView *viewToDraw = [ar_coordinateViews objectAtIndex:index];
viewToDraw.tag = index;
if ([self viewportContainsCoordinate:item]) {
CGPoint loc = [self pointInView:ar_overlayView forCoordinate:item];
CGFloat scaleFactor = 1.5;
if (self.scaleViewsBasedOnDistance) {
scaleFactor = 1.0 - self.minimumScaleFactor * (item.radialDistance / self.maximumScaleDistance);
}
float width = viewToDraw.bounds.size.width ;//* scaleFactor;
float height = viewToDraw.bounds.size.height; // * scaleFactor;
viewToDraw.frame = CGRectMake(loc.x - width / 2.0, loc.y-height / 2.0, width, height);
/*
CATransform3D transform = CATransform3DIdentity;
//set the scale if it needs it.
if (self.scaleViewsBasedOnDistance) {
//scale the perspective transform if we have one.
transform = CATransform3DScale(transform, scaleFactor, scaleFactor, scaleFactor);
}
if (self.rotateViewsBasedOnPerspective) {
transform.m34 = 1.0 / 300.0;
double itemAzimuth = item.azimuth;
double centerAzimuth = self.centerCoordinate.azimuth;
if (itemAzimuth - centerAzimuth > M_PI) centerAzimuth += 2*M_PI;
if (itemAzimuth - centerAzimuth < -M_PI) itemAzimuth += 2*M_PI;
double angleDifference = itemAzimuth - centerAzimuth;
transform = CATransform3DRotate(transform, self.maximumRotationAngle * angleDifference / (VIEWPORT_HEIGHT_RADIANS / 2.0) , 0, 1, 0);
}
viewToDraw.layer.transform = transform;
*/
//if we don't have a superview, set it up.
if (!(viewToDraw.superview)) {
[ar_overlayView addSubview:viewToDraw];
[ar_overlayView sendSubviewToBack:viewToDraw];
}
} else {
[viewToDraw removeFromSuperview];
viewToDraw.transform = CGAffineTransformIdentity;
}
[radarPointValues addObject:item];
index++;
}
float radius = [[[NSUserDefaults standardUserDefaults] objectForKey:#"radius"] floatValue];
if(radius <= 0 || radius > 100){
radius = 5.0;
}
radarView.pois = radarPointValues;
radarView.radius = radius;
[radarView setNeedsDisplay];
[radarPointValues release];
}
Any CoreGrapics or UI expert could give us his point of view about this issue??
You should either try to hittest as attached:
if ([self pointInside:point withEvent:event]) {
// do something
}
I would suggest you add the hit test on the superview, and do the following in the hit test of the parent of the markerViews
if ([markerView pointInside:point withEvent:event]) {
// extract the tag and show the relevant info
}
Hope this helps
I suppose it's not strictly in line with Apple guidelines but I guess it must be possible somehow. I'd like to change the height of navigation bar inside UINavigationController and the height of UIBarButtonItem elements inside that bar.
Using a trick from this question I managed to change the height of navigation bar but I can see no way of adjusting the height of bar button items.
If anyone knows how to change the size of bar button items, please help me out.
This is my solution. It works very well.
#interface UINavigationBar (CustomHeight)
#end
#implementation UINavigationBar (CustomHeight)
- (CGSize)sizeThatFits:(CGSize)size {
// Change navigation bar height. The height must be even, otherwise there will be a white line above the navigation bar.
CGSize newSize = CGSizeMake(self.frame.size.width, 40);
return newSize;
}
-(void)layoutSubviews {
[super layoutSubviews];
// Make items on navigation bar vertically centered.
int i = 0;
for (UIView *view in self.subviews) {
NSLog(#"%i. %#", i, [view description]);
i++;
if (i == 0)
continue;
float centerY = self.bounds.size.height / 2.0f;
CGPoint center = view.center;
center.y = centerY;
view.center = center;
}
}
Maybe this tutorial about a customized navbar will help you: Recreating the iBooks wood themed navigation bar
If you create a BarButtonItem with a UIImageView you can maybe change the framesize/boundsize of the custom UIImageView
UIImageView* imageView = [[[UIImageView alloc] initWithFrame:navigationController.navigationBar.frame] autorelease];
imageView.contentMode = UIViewContentModeLeft;
imageView.image = [UIImage imageNamed:#"NavBar-iPhone.png"];
[navigationController.navigationBar insertSubview:imageView atIndex:0];
So for your need you would give the -initWithFrame method appropriate values.
static CGFloat const CustomNavigationBarHeight = 74;
#implementation WTNavigationBar
- (CGSize)sizeThatFits:(CGSize)size{
size.width = 1024;
size.height = CustomNavigationBarHeight;
return size;
}
-(void)layoutSubviews {
[super layoutSubviews];
for (UIView *view in self.subviews) {
SFLog(#"view.class=%#",[view class]);
if ([view isKindOfClass:NSClassFromString(#"UINavigationItemButtonView")]) {
float centerY = self.bounds.size.height / 2.0f;
CGPoint center = view.center;
center.y = centerY;
view.center = center;
}
}
}
#end
in my iPad app,which has a fixed landscape orientation,I found I have to hardcode the size's width
I managed to do something similar by subclassing UINavigationBar and overriding -layoutSubviews. The code looks like:
-(void)layoutSubviews {
[super layoutSubviews];
int i = 0;
for (UIView *view in self.subviews) {
NSLog(#"%i. %#", i++, [view description]);
if ([view isKindOfClass:NSClassFromString(#"UINavigationButton")]) {
view.frame = CGRectMake(0, 0, 100, 50);
}
}
}
If you need to know how to subclass UINavigationBar, have a look at this very good answer.
I am not really sure about the NSClassFromString(#"UINavigationButton")] part. It works, but I did this as an experiment, and I'm not sure if this will get approved by Apple. I hope someone with a better knowledge might shed some light.
For the UINavigationbar
In iOS SDK 4.3 and beyond, there is a way (hack) to change the height of the UINavigationBar.
To change the height of UINavigationController, change its frame size in viewWillAppear:animated: function. Then, the height will stay customized throughout whole app.
For the UIBarButtonItems
I've actually run into this myself and the only thing I could come up with was leveraging initWithCustomView and passing in a UIButton with a defined frame.
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
/*
* Insert button styling
*/
button.frame = CGRectMake(0, 0, width, height);
UIBarButtonItem *barButtonItem = [[UIBarButtonItem alloc] initWithCustomView:button];
Otherwise UIBarButtonItem only has a width property that can be set but unfortunately not a height property. Another nifty thing I've done with initWithCustomView is to pass in a toolbar with a button and other things like activity indicators. Hope this helps.
How badly do you want this? And, how thin (or thick) do you want to make your navbar?
One approach would be to set the transform of the navbar to scale and translate it. If you scale it too much the title and button text will look wonky, but if you only need to shave a few pixels you might be allright.
Here's the result of scaling it to be 75% of full height (33 pixels tall):
And the code that produced this:
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.title = #"Thin Navigation Bar";
self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle: #"Press Me" style:UIBarButtonItemStyleBordered target: nil action: NULL ] autorelease];
CGFloat scale = .75;
CGFloat cy = 44.0 - ( 44.0 * scale );
self.navigationController.navigationBar.transform = CGAffineTransformScale( CGAffineTransformMakeTranslation( 0, -cy / 2.0 ), 1.0, scale ) ;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
CGFloat scale = .75;
CGFloat cy = 44.0 - ( 44.0 * scale );
CGRect r = self.view.frame;
r.origin.y -= cy;
r.size.height += cy;
self.view.frame = r;
}
Now, this does have a number of problems, which may or may not be solvable. #1 is that you're fighting with the UINavigationController to size and position the navbar and the view-controller views. Animating between view controllers that use this technique is likely going to look weird.
I'd be curious if you could solve the related issues...
One last thought: If you dont use a UINavigationController then there really aren't a whole lot of issues with this other than squished text. Or, you could use a navigation controller but hide the default navbar, and add the thin navbar to each of your child-view controller views. You could even subclass UINavigationBar and set the transform from within:
#interface TSThinNavBar : UINavigationBar
{
}
#end
#implementation TSThinNavBar
// assuming we'll always be constructed from a nib
- (id) initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder: aDecoder];
if ( self != nil )
{
CGFloat scale = .75;
CGFloat cy = 44.0 - ( 44.0 * scale );
self.transform = CGAffineTransformScale( CGAffineTransformMakeTranslation( 0, -cy / 2.0 ), 1.0, scale ) ;
}
return self;
}
#end
I'm currently using the method shown in this Cocoa with Love article to create a custom NSWindow subclass. As in the example, I needed to have a roughly 10px margin around the content of the window in order to draw an arrow (I'm creating a popover style window). I had to have the margin around the entire window instead of just the side with the arrow on it because I wanted to be able to change the arrow position without having to reposition the content.
To summarize, the method I'm using to do this is (relevant code is at the bottom):
Override the contentRectForFrameRect: and frameRectForContentRect:styleMask: methods of NSWindow to add the padding around the content:
Sets the custom drawn frame view of the window as the contentView and then overrides the setter and getter for the contentView so that the view that is passed in is added as a subview of the frame view.
The problem is that the autoresizing masks of views inside the actual content view of the window are completely messed up. Here is how I'm setting up the content in interface builder:
Here's how the autoresizing mask of the table view scroll view is set up:
And here's how the text label's autoresizing mask is set:
And here's what the result looks like in-app:
Relevant code (derived from the aforementioned article)
#define CONTENT_MARGIN 10.0
- (NSRect)contentRectForFrameRect:(NSRect)windowFrame
{
windowFrame.origin = NSZeroPoint;
return NSInsetRect(windowFrame, CONTENT_MARGIN, ICONTENT_MARGIN);
}
- (NSRect)frameRectForContentRect:(NSRect)contentRect
{
return NSInsetRect(contentRect, -CONTENT_MARGINT, -CONTENT_MARGIN);
}
+ (NSRect)frameRectForContentRect:(NSRect)contentRect
styleMask:(NSUInteger)windowStyle
{
return NSInsetRect(contentRect, -CONTENT_MARGIN, -CONTENT_MARGIN);
}
- (NSView*)contentView
{
return _popoverContentView;
}
- (void)setContentView:(NSView *)aView
{
if ([_popoverContentView isEqualTo:aView]) { return; }
NSRect bounds = [self frame];
bounds.origin = NSZeroPoint;
SearchPopoverWindowFrame *frameView = [super contentView];
if (!frameView) {
frameView = [[[SearchPopoverWindowFrame alloc] initWithFrame:bounds] autorelease];
[super setContentView:frameView];
}
if (_popoverContentView) {
[_popoverContentView removeFromSuperview];
}
_popoverContentView = aView;
[_popoverContentView setFrame:[self contentRectForFrameRect:bounds]];
[_popoverContentView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
[frameView addSubview:_popoverContentView];
}
I thought that maybe the popover content was going over the margins somehow, so I drew a border around the content view, but no, everything is as should be. The only issue is that the autoresizing masks of the label and table view inside the content view do not work as they should. Any advice is greatly appreciated.
EDIT: If anyone's interested, I've open-sourced the complete code for this popover window/controller on github as INPopoverController. Includes a sample project in case you want to try and reproduce the issue.
-( void )scaleWindowForHeight:( float )height
{
if (height > 22)
{
NSWindow* window = [self window];
NSRect old_window_frame = [window frame];
NSRect old_content_rect = [window contentRectForFrameRect: old_window_frame];
NSSize new_content_size = NSMakeSize( old_window_frame.size.width, height );
// need to move window by Y-axis because NSWindow origin point is at lower side:
NSRect new_content_rect = NSMakeRect( NSMinX( old_content_rect ), NSMaxY( old_content_rect ) - new_content_size.height, new_content_size.width, new_content_size.height );
NSRect new_window_frame = [window frameRectForContentRect: new_content_rect];
[window setFrame: new_window_frame display:YES animate: [window isVisible] ];
}
else
NSLog(#"window size too small");
}