Scrolling the map to user's location or clicking on an annotation on the map causes the container view move back to the place set in the storyboard - objective-c

I have
a view controller named MyMapViewController which is the delegate of the map view that is in the storyboard,
a view controller (i.e. a container view, 'myContainerView', in the storyboard) named MyListViewController that is the child view controller of MyMapViewController.
So, I have the following in the storyboard: http://pasteboard.co/1YAmM2ZU.png
In MyMapViewController, I set myContainerView's frame initially as follows so that the only part shown would be its navigation bar at the bottom of MyMapViewController:
CGRect destFrame = self.myContainerView.frame;
CGFloat navBarHeight = 44;
destFrame.origin.y = self.view.frame.size.height - navBarHeight;//to bottom of the screen; navigation bar yields 44 points
[UIView animateWithDuration:0
animations:^{
self.myContainerView.frame = destFrame;
}
];
In MyMapViewController, I set some annotations on the map and the delegate method is as follows:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>) annotation{
NSLog(#"welcome into the map view annotation");
// if it's the user location, just return nil.
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
// try to dequeue an existing pin view first
static NSString* AnnotationIdentifier = #"AnnotationIdentifier";
MKPinAnnotationView* pinView = [[MKPinAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier];
pinView.animatesDrop=YES;
pinView.canShowCallout=YES;
pinView.pinColor=MKPinAnnotationColorPurple;
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton setTitle:annotation.title forState:UIControlStateNormal];
[rightButton addTarget:self
action:#selector(showDetails:)
forControlEvents:UIControlEventTouchUpInside];
pinView.rightCalloutAccessoryView = rightButton;
UIImageView *profileIconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"annotationProfile.png"]];
pinView.leftCalloutAccessoryView = profileIconView;
return pinView;
}
The problem is that when I scroll the map to user's own location or to the location of an annotation that is not initially shown on the map or click on one of the annotations, myContainerView shows up suddenly. (it moves back to the place set in the storyboard.) However, I want myContainerView to stay where it was.
I came up with some possible explanations why it happens but am not quite sure how I can overcome this problem. Any suggestions?
Thanks in advance.

When you face a user interface abnormality such as the one in the question, then there is surely something you miss about the design. If this is the case, check if you solve the problem by manipulating the constraints you defined in the storyboard. You can bring the constraint into the code just by pressing ctrl and dragging it in your code and thus create a property. Then you can easily change the constraint's constant value in your code, for example for moving the related view to where you want in superview.
If this would not work, try implementing another UI design approach. For instance, you could create a .xib file for the view. This way, you can easily manipulate every single move of your view inside its superview just by changing its frame inside the superview. This approach gives you a much more elasticity than the one that in storyboard, but harder to implement.

Related

UIImageView autoresizingmask not working in certain cases

I am experimenting with a block-breaking iOS app to learn more about UI features. Currently, I am having issues trying to make it work for screen rotation.
I am able to get the blocks to re-arrange properly after screen rotation but am having trouble with getting the UIImageView for the paddle re-arrange.
My code is split as follows, VC calls initializes an object of the BlockModel class. This object stores a CGRect property (which is the CGRect corresponding to the paddle's ImageView).
The VC then creates an imageView initialized with the paddle image, sets the autoresinging property on the image view (to have flexible external masks), sets the frame based on the CGRect in the model object and adds the imageView as a sub-view of the main view being handled by the VC.
The code is below.
When I rotate, I am seeing that the ImageView is not being automatically repositioned.
If I do all the image view and CGRect creation in the vC, then it works (code sample 2).
Is this expected behavior? If yes, why is autoresizing not kicking in if the CGRect is obtained from a property in another object?
Full Xcode project code is here (github link)
EDIT
Looks like things don't work if I store the imageView as a property. I was doing this to have quick access to it. Why doesn't it work if imageView is stored as a property?
Code where model is initialized
self.myModel = [[BlockerModel alloc] initWithScreenWidth:self.view.bounds.size.width andHeight:self.view.bounds.size.height];
Model initialization code
-(instancetype) initWithScreenWidth:(CGFloat)width andHeight:(CGFloat)height
{
self = [super init];
if (self)
{
self.screenWidth = width;
self.screenHeight = height;
UIImage* paddleImage = [UIImage imageNamed:#"paddle.png"];
CGSize paddleSize = [paddleImage size];
self.paddleRect = CGRectMake((self.screenWidth-paddleSize.width)/2, (1 - PADDLE_BOTTOM_OFFSET)*self.screenHeight, paddleSize.width, paddleSize.height);
}
return self;
}
Code in VC where imageView is initialized
self.paddleView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"paddle"]];
self.paddleView.backgroundColor = [UIColor clearColor];
self.paddleView.opaque = NO;
self.paddleView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin|UIViewAutoresizingFlexibleBottomMargin;
NSLog(#"Paddle rect is %#",NSStringFromCGRect(self.myModel.paddleRect));
[self.paddleView setFrame:self.myModel.paddleRect];
[self.view addSubview:self.paddleView];
If I instead use this code in the VC to initialize imageView things work
UIImage* paddleImage = [UIImage imageNamed:#"paddle.png"];
CGSize paddleSize = [paddleImage size];
CGRect paddleRect = CGRectMake((self.view.bounds.size.width-paddleSize.width)/2, (1 - PADDLE_BOTTOM_OFFSET)*self.view.bounds.size.height, paddleSize.width, paddleSize.height);
UIImageView *paddleView = [[UIImageView alloc] initWithImage:paddleImage];
paddleView.backgroundColor = [UIColor clearColor];
paddleView.opaque = NO;
paddleView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin|UIViewAutoresizingFlexibleBottomMargin;
[paddleView setFrame:paddleRect];
[self.view addSubview:paddleView];
Found the issue. I was using the model object to handle all my "game object location" logic. E.g VC would calculate the X axis deltas from the touch events & forward them to the model object. Also, CADisplayLink events would be forwarded so that model can update ball location based on velocity and time since last event. It will then use updated location to detect collisions. This split was used because model class also had the methods to detect collisions with sides, paddle/ball etc.
The issue was that the model object was rewriting the CGRect of the paddleView by adding the delta it received from VC to the origin.x of current paddleRect it had stored. This paddleRect did not take into account the automatic adjustment to the CGRect that is done by auto-resizing after a rotation.
The fix was for the VC to set the CGRect of the paddleRect (set to paddleView frame) before calling the method in the model to update all the game properties and detect collisions. This way model only takes care of logic of collusion detection and updating ball movement and velocity based on it. The VC uses current paddleView location and hence automatically accounts for the automatic adjustment to the CGRect that is done by auto-resizing after a rotation.
Source code in github link updated.

UIButton inside UIView doesn't respond to touch events

I've put a UIButton inside a custom UIView and the button is not receiving any touch events (it doesn't get into the highlighted state, so my problem is not about being unable to wire up a touch inside up handler). I've tried both putting it into the XIB in Interface Builder, and also tried programatically adding the UIButton into the UIView seperately, both ended with no luck. All my views are inside a UIScrollView, so I first though UIScrollView may be blocking them, so I've also added a button programatically exactly the same way I add my custom view into UIScrollView, and the button worked, elimination the possibility of UIScrollView could be the cause. My View's hiearchy is like this:
The button is over the image view, and the front layer isn't occupying my button completely, so there's no reason for me not be physically interacting with the button. At my custom view's code side, I'm creating my view as such:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
UIView *sub = [[[NSBundle mainBundle] loadNibNamed:#"ProfileView" owner:self options:nil] objectAtIndex:0];
[self addSubview:sub];
[sub setUserInteractionEnabled:YES];
[self setUserInteractionEnabled:YES];
CALayer *layer = sub.layer;
layer.masksToBounds = YES;
layer.borderWidth = 5.0;
layer.borderColor = [UIColor whiteColor].CGColor;
layer.cornerRadius = 30.0;
/*layer.shadowOffset = CGSizeZero;
layer.shadowRadius = 20.0;
layer.shadowColor = [[UIColor blackColor] CGColor];
layer.shadowOpacity = 0.8;
*/
}
return self;
}
I've tried all combinations of setUserInteractionsEnabled, and had no luck. (Yes, also set them to checked in Interface Builder too). I've also read in another question with a similar problem that I should try overriding 'canBecomeFirstResponder' to return 'YES' and I've also done that too. But the problem persists, I can't click the button. I've not given any special properties, settings to the button, it's just a regular one. My other objects in the view (labels below, image view behind the button etc.) are working properly without problems. What could be possibly wrong here?
Thanks,
Can.
UPDATE: Here is a quick reproduction of the problem: https://dl.dropbox.com/u/79632924/Test.zip
Try to run and click the button.
Looking at the test project, I believe your problem in the way you create TestView, you do not specify the frame for it, so basically the parent view is 0 size, and the subviews you see from XIB extending out of the parent view and thus do not get anything in responder chain.
You should either specify the frame when creating TestView, or adjust the frame after loading XIB file.
I have had this problem as well. The cause for me was that the UIButton superview frame was of height 0, so I believe that even though a touch was happening, it was not being passed down to the button.
After making sure that the button's superview took a larger rectangle as a frame the button actions worked.
The root cause for this problem on my side was a faulty auto layout implementation (I forgot to set the height constraint for the button's superview).
I've found the solution. I was initializing my custom view as:
MyView *view = [[MyView alloc] init];
I've initialized it instead with a frame of my view's size, and it started responding to events:
CGRect rect = CGRectMake(0,0,width,height);
MyView *view = [[MyView alloc] initWithFrame:rect];
Storyboard Solution
Just for anyone wanting a solution to this when using storyboards and constraints.
Add a constraint between the superview (containing the button) and the UIButton with an equal heights constraint.
In my case, I had selected embed UIButton in a UIView with no inset on the storyboard. Adding the additional height constraint between the UIButton and the superview allowed the UIButton to respond to touches.
You can confirm the issue by starting the View Debugger and visually confirm that the superview of the UIButton is not selectable.
(Xcode 11, *- Should also work in earlier versions)

Button not working in UITableViewCell subclass when it is a subview of a view inside the UITableViewCell

I have a button in a tableView Cell that I need to respond to touch events. Normally this would easily be solved by
[cell.playButton addTarget:self action:#selector(playButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
inside of -tableView:cellForRowAtIndexPath:
The problem I am having is that my button is inside of a custom UITableViewCell subclass and is also the subview of a view that I am creating inside of that class..
for example:
UIView *view = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
self.theView = view;
[self addSubview:view];
[view release];
UIButton *playButton = [UIButton buttonWithType:UIButtonTypeCustom];
playButton.frame = CGRectMake(5, 5, 100, 30);
[playButton setImage:[UIImage imageNamed:#"button_playvid.png"] forState:UIControlStateNormal];
[playButton setImage:[UIImage imageNamed:#"button_playvid_active.png"] forState:UIControlStateHighlighted];
self.playButton = playButton;
[self.theView addSubview:playButton];
When the button is a subview of a view that I create within the custom UITableViewCell then the cell is highlighted instead of the button being pressed so the target that I setup in -tableView:cellForRowAtIndexPath is never called. Not sure why.. any help?
Thanks,
Joel
PS. I realize that there probably isn't a practical reason to create a background view exactly like this since there already is one. This is just example code that I'm using to explain a problem I'm having.. :D
Thanks for Looking!
The UIView you are adding is actually a UIImageView. From the docs:
New image view objects are configured to disregard user events by default. If you want to handle events in a custom subclass of UIImageView, you must explicitly change the value of the userInteractionEnabled property to YES after initializing the object.
I think that either userInteractionEnabled cascades to subviews or the superview will not pass the touch to its subviews in this case, so your button will not be receiving touches. Set your image view to have .userInteractionEnabled = YES and you should be fine.
I had the same problem and tried EVERYTHING. The thing that finally did it was implementing the method
(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
Just return whatever the height is of your custom cell (look in the NIB file)
Recently I've ran into such issue with legacy code. The reason was that earlier we used to add subviews to custom UITableViewCell instance itself and now we have to add to contentView of the cell instance.
I fixed it by changing:
addSubview(increaseButton)
to
contentView.addSubview(increaseButton)
Of course, isUserInteractionEnabled should be true.
Instead of button, you could try the same with an image. You could define which method to be called when the image is clicked.

Advice for horizontal scroll view IOS

I'm trying to create horizontal scroll view(s). Each view containing one image. [done]
Initially when app is opened I'd display couple of images i.e 3, so user can scroll back and forth between images. [done]
However I want to be able to go to another view controller and pick another image(s) maybe two for example and display five images in the scroll view instead of displaying 3 initially.
How would one do that? to "re-refresh" the initial scroll view ?
Update
Should I use delegate for this communication between view controllers? or how is this done?
1 main controller, other containing image selection?
This part above and much more explained by article here.(not advertising, I hope it will help someone as well).
Bounty update part 1 :
I think now that I found my way around delegates, I have additional question that I cannot find answer to, all examples I saw were about updating table views.
Bounty part 2 :
If I were to have a scrollview inside my view controller and some nsimages inside scroll view. i.e :
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray *images = ...some images array;
for (int i = 0; i < images.count; i++) {
CGRect frame;
frame.origin.x = self.scrollView.frame.size.width * i;
frame.origin.y = 0;
frame.size = self.scrollView.frame.size;
UIImageView *subview = [[UIImageView alloc] initWithFrame:frame];
subview.image = [images objectAtIndex:i];
[self.scrollView addSubview:subview];
}
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width * images.count, self.scrollView.frame.size.height);
}
And let say my view controller implemented some delegate method didAddImage or didRemoveImage.
Meaning that about images array would get updated.
Actual bounty question :
How would one actually "tell" the view controller, O.K now your scrollview has one more image to display, please re-fresh or reload?
If I were to have a table view instead scroll view and was inserting images I'd do it something like this(in my delegate method) :
-(void) mockDelegatetablemethod:(...) ...{
[self.images addObject:image];
NSIndexPath *indexPath =
[NSIndexPath indexPathForRow:[self.images count] - 1
inSection:0];
[self.tableView insertRowsAtIndexPaths:
[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationAutomatic];
[self dismissViewControllerAnimated:YES completion:nil];
}
How would one do this to the scrollview?
Bounty update part 3:
This is "simple" case described above, naturally I'd have to support removing images also as well as if one wants to remove all images and add some new.
How would one actually "tell" the view controller, O.K now your
scrollview has one more image to display, please re-fresh or reload?
Well, you would just add the new image as a subview of the scroll view. So something like:
- (void)addImage:(UIImage*)image {
[images addObject:image];
CGRect frame;
frame.origin.x = self.scrollView.frame.size.width * images.count;
frame.origin.y = 0;
frame.size = self.scrollView.frame.size;
UIImageView *subview = [[UIImageView alloc] initWithFrame:frame];
subview.image = image;
[self.scrollView addSubview:subview];
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width * images.count, self.scrollView.frame.size.height);
}
Or you could have a method that goes and "resets" the scroll view by removing all subviews and then re-adds all the images in your images array. Then call this method in viewDidLoad instead of your code there so that you're not duplicating code.
Basically, there's no silver bullet here. You roll your own.
You're going to need make your image scroller a class that you can tell to load up certain images. You'll need a reference to the existing image scroller, pass that pointer into your other "pick more images" controller. Then the "pick more images" controller can tell the image scroller that there is a new list of images.
This is already implemented by Apple in their iOS sample project "PhotoScroller". It has view reuse as well. They even have a WWDC 2010 video on this. I think its advanced scrollview techniques OR designing apps with scrollviews.
Shouldn't be hard to port over to mac

Why doesn't UIView.exclusiveTouch work?

In one of my iPhone projects, I have three views that you can move around by touching and dragging. However, I want to stop the user from moving two views at the same time, by using two fingers. I have therefore tried to experiment with UIView.exclusiveTouch, without any success.
To understand how the property works, I created a brand new project, with the following code in the view controller:
- (void)loadView {
self.view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
UIButton* a = [UIButton buttonWithType:UIButtonTypeInfoDark];
[a addTarget:self action:#selector(hej:) forControlEvents:UIControlEventTouchUpInside];
a.center = CGPointMake(50, 50);
a.multipleTouchEnabled = YES;
UIButton* b = [UIButton buttonWithType:UIButtonTypeInfoDark];
[b addTarget:self action:#selector(hej:) forControlEvents:UIControlEventTouchUpInside];
b.center = CGPointMake(200, 50);
b.multipleTouchEnabled = YES;
a.exclusiveTouch = YES;
[self.view addSubview:a];
[self.view addSubview:b];
}
- (void)hej:(id)sender
{
NSLog(#"hej: %#", sender);
}
When running this, hej: gets called, with different senders, when pressing any of the buttons - even though one of them has exclusiveTouch set to YES. I've tried commenting the multipleTouchEnabled-lines, to no avail. Can somebody explain to me what I'm missing here?
Thanks,
Eli
From The iPhone OS Programming Guide:
Restricting event delivery to a single view:
By default, a view’s exclusiveTouch property is set to NO. If you set
the property to YES, you mark the view so that, if it is tracking
touches, it is the only view in the window that is tracking touches.
Other views in the window cannot receive those touches. However, a
view that is marked “exclusive touch” does not receive touches that
are associated with other views in the same window. If a finger
contacts an exclusive-touch view, then that touch is delivered only if
that view is the only view tracking a finger in that window. If a
finger touches a non-exclusive view, then that touch is delivered only
if there is not another finger tracking in an exclusive-touch view.
It states that the exclusive touch property does NOT affect touches outside the frame of the view.
To handle this in the past, I use the main view to track ALL TOUCHES on screen instead of letting each subview track touches. The best way is to do:
if(CGRectContainsPoint(thesubviewIcareAbout.frame, theLocationOfTheTouch)){
//the subview has been touched, do what you want
}
I was encountering an issue like this where taps on my UIButtons were getting passed through to a tap gesture recognizer that I had attached to self.view, even though I was setting isExclusiveTouch to true on my UIButtons. Upon reviewing the materials here so far, I decided to put some code in my tap gesture code that checks if the tap location is contained in any UIButton frame and if that frame is also visible on the screen at the same time. If both of those conditions are true, then the UIButton will already have handled the tap, and the event triggered in my gesture recognizer can then be ignored as a pass through of the event. My logic allows me to loop over all subviews, checking if they are of type UIButton, and then checking if the tap was in that view and the view is visible.
#objc func singleTapped(tap: UITapGestureRecognizer)
{
anyControlsBreakpoint()
let tapPoint = tap.location(in: self.view)
// Prevent taps inside of buttons from passing through to singleTapped logic
for subview in self.view.subviews
{
if subview is UIButton {
if pointIsInFrameAndThatFrameIsVisible(view: subview, point: tapPoint)
{
return // Completely ignores pass through events that were already handled by a UIButton
}
}
}
Below is the code that checks if point was inside a visible button. Note that I hide my buttons by setting their alpha to zero. If you are using the isHidden property, your logic might need to look for that.
func pointIsInFrameAndThatFrameIsVisible(view : UIView, point : CGPoint) -> Bool
{
return view.frame.contains(point) && view.alpha == 1
}