EDIT:
Immediately after posting this question, I found ONE line of code I missed commenting out. It was this... mapViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
I commented it out, and the map is working again.
Do you have any idea why that line of code would kill the maps gesture recognizers?
I have an MKMapView that was working perfectly fine in recent months, and quit dead today.
The only thing I did today, messing with the code, was to play with some orientation code in viewWillTransitionToSize:withTransitionCoordinator: and viewWillLayoutSubviews That's IT !!!
I commented out everything I did there, and still it's dead.
It not only won't respond to my own gesture recognizer, it won't respond to any of its own recognizers... pinch, rotate, etc. It's just frozen.
I have NO views on top of the map that would obstruct touches either.
I have control-clean(ed) my build folder.
I have closed XCode project and wiped out it's derived data folder for said project.
#implementation MapViewController
{
MKMapView *appleMapView;
}
-(void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
[self addMapViewToSelf];
}
-(void)addMapViewToSelf
{
// Create and add the apple map view to the side menu view.
appleMapView = [[MKMapView alloc] initWithFrame:self.view.frame];
[self.view addSubview:appleMapView];
appleMapView.delegate = self;
UILongPressGestureRecognizer *tapRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(windowTapped:)];
tapRecognizer.minimumPressDuration = 1.0f;
tapRecognizer.delegate = self;
[appleMapView addGestureRecognizer:tapRecognizer];
}
-(void)windowTapped:(UIGestureRecognizer *)recognizer
{
NSLog(#"The recognizer state is '%ld'", (long)recognizer.state);
if (recognizer.state != UIGestureRecognizerStateBegan) {
return;
}
}
AutoResizingMask is the property by by the UI Elements (eg. Label) shift when the boundaries change. Means when orientation is change the app adapts and does not crash. But if you will set autoResizingMask to Constraint, you are telling it to change constraints as per the autoResizingMask. This is the default behaviour of every UI Element. This helps us to quickly and dynamically arrange UI Elements with methods like self.view.centre or self.view.frame or self.label.size.width etc.
Setting translatesAutoresizingMaskIntoConstraints = NO you are actually removing this behaviour. Therefore you must provide the UI Element fixed co-ordinates, so that the UI Element stays there no matter the orientation of screen. But you are not doing so. So the UIElement has no constraints or a fixed location and behave erractically.
You must not set it to NO, if you don't want to play dynamically with UI's location or size. Like input based growing textfield or some crude animation.
Here is Apple trying to tell you the same thing.
When a view’s bounds change, that view automatically resizes its
subviews according to each subview’s autoresizing mask. You specify
the value of this mask by combining the constants described in
UIViewAutoresizing using the C bitwise OR operator. Combining these
constants lets you specify which dimensions of the view should grow or
shrink relative to the superview. The default value of this property
is UIViewAutoresizingNone, which indicates that the view should not be
resized at all.
When more than one option along the same axis is set, the default
behavior is to distribute the size difference proportionally among the
flexible portions. The larger the flexible portion, relative to the
other flexible portions, the more it is likely to grow. For example,
suppose this property includes the UIViewAutoresizingFlexibleWidth and
UIViewAutoresizingFlexibleRightMargin constants but does not include
the UIViewAutoresizingFlexibleLeftMargin constant, thus indicating
that the width of the view’s left margin is fixed but that the view’s
width and right margin may change. Thus, the view appears anchored to
the left side of its superview while both the view width and the gap
to the right of the view increase.
If the autoresizing behaviors do not offer the precise layout that you
need for your views, you can use a custom container view and override
its layoutSubviews method to position your subviews more precisely.
I do such mistakes a million times , this blog has a nice solution to it.
https://www.innoq.com/en/blog/ios-auto-layout-problem/
Related
I have a custom view, CustomView, that can animate itself in response to an animate message:
// in a view controller
[self.customView animate];
Inside -[CustomView animate], the logic is as follows:
- (void)animate {
for each subview {
startFrame = subview.frame;
endFrame = EndFrameFromStartFrame(startFrame);
subview.animation = AnimationLoopBetween(startFrame, endFrame);
}
}
* This block is pseudocode.
That is to say, the animate method examines the current frame of each subview and generates an animation ending frame for each subview which is essentially a frame which is a fixed ratio larger than the current frame. Then, it creates one animation per subview that varies the frame of its subview, alternating between the current (or start) frame and the end frame, calculated earlier.
This method works very well in general. It allows CustomView to animate properly regardless of how its superview positions and sizes it.
However, in a few cases, "users" of CustomView (that is, view controllers) call animate very early in their lifetime – so early, in fact, that Auto Layout has not yet made any constraint or layout passes. This means that animate is called when all of CustomView's subview frames are CGRectZero. As you can probably guess, this causes both the starting and ending values of the animations to be calculated incorrectly.
Even after the layout pass is complete, and the views' model layers have the proper frames, the CustomView instance is still not visible, because its subviews are happily animating from one zero-rect to another. Not good.
How should I restructure this animation behavior to allow users of this class to call animate at any time without negative repercussions?
Here are some possible answers I've considered, and why they don't seem satisfactory to me:
In any view controller that uses CustomView, put off the call to animate until viewDidLayoutSubviews.
This forces an unwieldy restriction on view controllers that use this view.
In CustomView's layoutSubviews method, first call [super layoutSubviews], then restart the animation if it was already running.
An interesting idea, but CustomView has more than one level of subviews. Thus, even after [super layoutSubviews], many of the views further down the view hierarchy will not yet have valid frames, meaning animate will still misbehave.
In CustomView's layoutSubviews method, first call [super layoutSubviews], then manually layout any required views' frames using [<view> layoutIfNeeded], before finally restarting the animation if it was already running.
This might actually work, but doesn't seem like a good idea. As much as possible, I'd like to leave layout work to Auto Layout.
Why not just skip the uninteresting case?
- (void)animate {
if (CGRectEqualToRect(self.frame, CGRectZero) return;
// ...animate.
}
In an iOS app, I am adding a subview to my main view with:
[self.view addSubview:firstUIImageSubview];
However in the subview class firstUIImageSubview, I am creating and adding another subView (secondUIImageSubview) that I would like to put below the first subview with:
[self insertSubview:secondUIImageSubview belowSubview:self];
My problem is that the second subview is displayed above the first subview when I want to have it below. How is it possible to achieve that ? Thanks.
This should do the trick.
[self.superview insertSubview:secondUIImageSubview atIndex:0];
When you use insertSubview:belowSubview: it places the subview in regards to other subviews that particular object manages.
[self insertSubview:secondUIImageSubview belowSubview:self];
Doesn't make much sense. Although self is a UIView (or a subclass) it still should never manage itself as a subview. Therefore
[self insertSubview:secondUIImageSubview belowSubview:firstUIImageSubview];
is probably what you want. But remember this will only place the secondUIImageSubview below firstUIImageSubview in terms of its Z-Index (it's depth on the screen). If you want it to be physically placed below firstUIImageSubview (IE it's XY coordinate) then you need to set it's position using subview's frame or setting its origin instead (by manipulating it's center or anchor points for instance).
I am working with a custom view which inherits from UIScrollView. The problem I am having is adding a UILabel that should be displayed at the 'top', in front of the other views. It works, but unfortunately it also covers up the scroll indicators.
As you can see, the scroll indicator is being obscured. I don't want to disable scroll indicators.
I am adding the views directly to the UIScrollView:
UILabel *subsection = [[UILabel alloc] initWithFrame:CGRectMake(0, y * h,
[self totalContentWidth], [tableViewDelegate rowHeight])];
[subsection setText:subsectionName];
[subsection setTextAlignment:NSTextAlignmentCenter];
[subSectionRows setValue:subsection forKey:key];
[self addSubview:subsection];
and then bringing them to the front, which
- (void) bringSubsectionsToFront {
for (UIView* row in [self.subSectionRows allValues]) {
[self bringSubviewToFront:row];
}
}
This behavior is confusing to me. If I peek at UIView.layer.zPosition of all the UIScrollView's subviews, they are all the same.
If I adjust the bringSubsectionsToFront method to instead move the labels in front of the view that contains the grid lines, the behavior is the same.
Looking at some internal view classes whose behavior works, it looks as if they are being added to the scroll view with: [self insertSubview:cell atIndex:0];
What am I missing here?
Solution:
verec remarked that I could find the bars if I really wanted to. So I iterated over the subview list at a suitable point, and sure enough, there they were. The order of the objects was as follows:
CustomCell
...
UIImageView
UIImageView
CustomBorderView
UILabel
UILabel
Assuming this is the z ordering, no wonder my labels were always on top.
Solution becomes simple; add UILabel's after CustomCells. This solution only works because the custom table adds CustomCells at index 0, which I believe makes scroll indicators appear after the CustomCells. If this assumption holds, I can reorder the other views relative to the CustomCells, achieving the layering effect I want.
The simplest is to use and intermediate UIView, call it 'contentView' whose children are the views you want to bringToFront.
If that contentView is what is inside the scrollView, it will stay 'below' the scroll indicators whichever contentView child you bring to the front.
The alternative would be to scan into the scrollView looking for the scroll indicators (you will find them!) but that's a compatibility risk, as you never know if/when Apple may decide to remove/reshape/reposition them according to some new (flatter ...) design paradigm ...
I am trying to setup a simple view that displays an image and allows users to zoom in/out and pan the image as they desire.
I have autoLayout enabled and the view consists of a UIScrollView with an embedded UIImageView.
Both views are sized to the entire screen.
I used the logic and code from Apple's ScrollViewSuite (the "1_TapToZoom" sample) and everything seems correct. However, when pinching the view, the ScrollView/ImageView gets all distorted and doesn't respond properly. Hard to explain the behavior, but lets just call it "funky". The size and aspect changes in no consistent manner.
My GUESS is that this has to do with autolayout constraints.
Does anyone have some suggestions/fixes for this?
The issues that occurs is the changing of location of the imageview during the zoom process. The origin location of the imageview will change to be a negative value during the zoom. I believe this is why the jerky movement occurs. As well, after the zoom is complete the imageView is still in the wrong location meaning that scrolls will appear to be offset.
If you implement -(void) scrollViewDidZoom:(UIScrollView *)scrollView and log the frame of the UIImageView during this time you can see its origin changing.
I ended up making things work out by implementing a strategy like this: https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/ch20p573scrollViewAutoLayout/ch20p573scrollViewAutoLayout/ViewController.m
And in addition changing the frame of the contentView while zooming
-(void) scrollViewDidZoom:(UIScrollView *)scrollView {
CGRect cFrame = self.contentView.frame;
cFrame.origin = CGPointZero;
self.contentView.frame = cFrame;
}
I have a situation similar to these two posts (1907297 AND 689684) and to describe my situation most concisely, I present this text/graphical layout (similar to what you'd see in IB, dots used to enforce indent levels)
UIView (MainView: 320x460)
. .UIScrollView (ScrollView: 320x460)
. .UIView (OverlayView: 320x40)
. . . .UIButton (ArbitraryButton1)
. . . .UILabel (ArbitraryLabel1)
. . . .UILabel (ArbitraryLabel2)
The goal here is for the OverlayView to serve as a unified, transparent container to position and display some arbitrary buttons/labels on top of the ScrollView. These buttons/labels should remain stationary while the content in the ScrollView beneath moves with user swipes. The buttons/labels may sometimes be hidden/unhidden/scaled in unison (with animation) which is what makes it handy to have them all grouped in the single OverlayView.
The trouble is that, while taps on the OverlayView seem to transmit right through to the underlying ScrollView just nicely, swiping motions have no effect. I can detect/intercept the swipes by overriding the
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
method in the OverlayView, however I haven't yet found a way to properly pass those along to the ScrollView in a way that makes it scroll. Evidently the touchesMoved method is not what UIScrollView uses to detect/interpret swipes?
All the other similar posts I've researched have either found a different solution that wouldn't work in my case or have just gone unsolved. I've also seen mention of employing touchesShouldBegin / touchesShouldCancel though I don't grasp how that would be implemented. Anyhow, still hopeful that there's some insight from the community that can allow me to come up with an elegant solution for this - any sample code would be fantastic.
Thanks in advance, Joel.
P.S. - I should also mention that I need to make this compatible with iOS 3.0 so I think trying to use UIGestureRecognizers is out.
Here's an easier solutions that worked well for me:
In the OverlayView (the view on top of UIScrollView), make the width and height both 0 (so that the view is no longer technically on top of the scrollview) and set clipsToBounds = NO (so that the contents of the OverlayView still show up on top of the scrollview). It worked like a charm for me.
self.OverlayView.clipsToBounds = NO;
CGRect frame = self.OverlayView.frame;
self.OverlayView.frame = CGRectMake(frame.origin.x, frame.origin.y, 0, 0);
Note that if OverlayView contains interactive controls (like the button above) then they will no longer work. You'll need to move it into it's own view above the UIScrollView.
How about, at runtime in viewDidLoad you take the buttons out of the container view and place them in the view as subviews directly (and get rid of the container view)? Then there's no container view to intercept swipes but you can still use a view to group things in IB.
Also potentially you could put the container view in as a subview of the scroll view instead, and in the scroll view delegate keep re-positioning the view whenever the user scrolls. That would seem to have a high potential for being jittery but may be worth a try.
Also if the containing view is a visual container and you need to see it, you could instead use a CALayer that was placed in the superview on top of the CALayer for rendering the scroll view, since CALayers have nothing to do with input and would not each touches.
You should subclass UIScrollView and override the touchesShouldCancelInContentView: method
-(BOOL)touchesShouldCancelInContentView:(UIView *)view
{
if ([view isKindOfClass:[UIButton class]]) {//or whatever class you want to be able to scroll in
return YES;
}
if ([view isKindOfClass:[UIControl class]]) {
return NO;
}
return YES;
}