dynamically creating UIImageViews in a custom class method call in xcode - objective-c

i want to dynamically create and add to the view a uiimageview when a touch is registered within the frame of another uiimageview. i want these to stay on the screen and remain moveable when a touch is registered inside the frame of each uiimageview. here is the code i have now but it doesn't quite work right and is really glitchy(i.e. i can pick up a new view and it is created but it doesn't follow the touch properly as the touch is moved:
image = [UIImage imageNamed:#"1.png"];
UIImageView *onePieceCopy = [[UIImageView alloc] initWithImage:image];
onePieceCopy.frame = CGRectMake(currentPos.x, currentPos.y, 75, 75);
[self addSubview:onePieceCopy];
if (CGRectContainsPoint([onePieceCopy frame], position)) {
onePieceCopy.center = position;
[self bringSubviewToFront:onePieceCopy];
}
[onePieceCopy release];
this code is in a switch statement that is in a method that is called when a touch is registered inside the frame of a designated UIImageView.
the ideal result would be a system not unlike a map editor where you drag parts from a 'staging' area and onto the map, if that makes any sense. does anyone know how to do this or how i can better my code to get the desired result

WWDC 2011 (must be a registered developer). Watch "Session 104 - Advanced Scroll View Techniques". At about minute 40 they do a nice pick and place of a uiimage.

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.

iOS 8 custom view controller presentation: changing size of presented VC during animation

I'm writing an app with a series of cards in a table view, similar to the layout of the Google app for iOS when Google Now cards are enabled. When the user taps on a card, there should be a custom transition to a new view controller which is basically the card looking bigger, almost filling the screen, and it has more details on it. The custom transition itself should look like the card is animated upward and growing in size until it reaches its final size and position, which is now the new view controller holding the card.
I have been trying to approach this using a custom view controller transition. When the card is tapped, I initiate a custom view controller transition with UIModalPresentationCustom, and I set a transition delegate, which itself vends a custom animator and a custom UIPresentationController. In animateTransition:, I add the new view controller's view into the container view, setting the frame to the card's frame initially (so it looks like the card is still there and unchanged). Then I attempt to perform an animation where the presented view's frame grows in size and changes in position so that it moves into its final position.
Here's some of my code which does what I've described above - I'm trying to keep it short and sweet, but I can provide more info if needed:
Transition Delegate
-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
// NOWAnimationDelegate is my own custom protocol which defines the method for asking the presenting VC for the tapped card's frame.
UIViewController<NOWAnimationDelegate> *fromVC = (UIViewController<NOWAnimationDelegate> *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *finalVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
// Ask the presenting view controller for the frame of the tapped card - this method works.
toView.frame = [fromVC rectForSelectedCard];
[transitionContext.containerView addSubview:toView];
CGRect finalRect = [transitionContext finalFrameForViewController:finalVC];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
toView.frame = finalRect;
}completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
Custom UIPresentationController
-(CGSize)sizeForChildContentContainer:(id<UIContentContainer>)container withParentContainerSize:(CGSize)parentSize {
return CGSizeMake(0.875*parentSize.width, 0.875*parentSize.height);
}
-(CGRect)frameOfPresentedViewInContainerView {
CGRect presentedViewFrame = CGRectZero;
CGRect containerBounds = self.containerView.bounds;
presentedViewFrame.size = [self sizeForChildContentContainer:(UIView<UIContentContainer> *)self.presentedView withParentContainerSize:containerBounds.size];
presentedViewFrame.origin.x = (containerBounds.size.width - presentedViewFrame.size.width)/2;
presentedViewFrame.origin.y = (containerBounds.size.height - presentedViewFrame.size.height)/2 + 10;
return presentedViewFrame;
}
What I'm finding is happening is that the new view is being automatically set to its final size immediately at the start of the animation, and then the animation is just the new view animating upwards. Using breakpoints, I'm noticing that frameOfPresentedViewInContainerView is called during the [transitionContext.containerView addSubview:toView] call, which would probably explain why this is happening - frameOfPresentedViewInContainerView returns "The frame rectangle to assign to the presented view at the end of the animations" according to the UIPresentationController documentation.
However, I'm not sure how to proceed, or if it's even really possible. All the examples of custom view controller transitions I've seen have all had the final size of the presented view controller static and unchanging during the animation. Is there any way to perform a custom view controller transition with a changing size of the presented view during the animation, or do I have to approach this in a different way?
Basically what you need to do is animating your toView.transform using CGAffineTransform in your Transition Delegate. Steps to do:
Before animating set your toView.frame = your frame when it's not showing
Create, let's say, CGAffineTransformMakeScale to scale from hidden frame to your desired final presented frame
On animation block set toView.transform to the transform that you create on step 2
As Aditya mentions CGAffineTransform is the way to go here.
Ive got this working now with it maintaining the width:
CGFloat targetscale=initialHeight/finalHeight;
CGFloat targetyoffset=(finalHeight-(finalHeight*targetscale))/2;
int targety=roundf(initialPosition-targetyoffset);
CGAffineTransform move=CGAffineTransformMakeTranslation(0, targety);
CGAffineTransform scale=CGAffineTransformMakeScale(1,targetscale);
toView.transform=CGAffineTransformConcat(scale,move);
This positions the incoming view taking into account the determined scale and then performs both transforms concurrently so the view scales & moves to the final position and size.
You then just set
toView.transform=CGAffineTransformIdentity;
in the animation block and it'll scale to the final location and size.
Note, this only moves and scales in the vertical dimension but can be adapted to scale in all directions like so:
+(CGAffineTransform)transformView:(UIView*)view toTargetRect:(CGRect)rect{
CGFloat targetscale=rect.size.height/view.frame.size.height;
CGFloat targetxoffset=(view.frame.size.width-(view.frame.size.width*targetscale))/2;
int targetx=roundf(rect.origin.x-view.frame.origin.x-targetxoffset);
CGFloat targetyoffset=(view.frame.size.height-(view.frame.size.height*targetscale))/2;
int targety=roundf(rect.origin.y-view.frame.origin.y-targetyoffset);
CGAffineTransform move=CGAffineTransformMakeTranslation(targetx, targety);
CGAffineTransform scale=CGAffineTransformMakeScale(targetscale,targetscale);
return CGAffineTransformConcat(scale, move);
}
And don't forget to transform the cell's rect the global coordinate space so the start frame is correct for the whole window not just the cells position in the table.
CGRect cellRect=[_tableView rectForRowAtIndexPath:[_tableView indexPathForSelectedRow]];
cellRect=[self.tableView convertRect:cellRect toView:presenting.view];
hope this helps!
Apple provides a wwdc sample app 'LookInside', accompanying talk 228: 'A look inside presentation controllers'. It features 2 custom presentations, of which one animates the size of the presented view controller. A look inside that code should help you out ;)

iOS: Hiding UIView objects

I'm trying to implement a UIDatePickeron Storyboard. As you would expect, the date picker can't be on screen when the view first loads so I have to write the custom code to move it off screen on load. Here's the code...
- (void)hideDatePicker
{
CGRect newRect = self.datePicker.frame;
CGPoint newOrigin = CGPointMake( 0, [[UIScreen mainScreen] bounds].size.height);
newRect.origin = newOrigin;
self.datePicker.frame = newRect;
}
I am currently calling this method in viewDidLoad.
When I run the app, the date picker is still on the bottom of the screen, disgustingly taking up half the screen...
You may want to implement it inside an UIActionSheet, a not-so-difficult and elegant solution. Check out this question that will provide you a brief explanation and the necessary code. Add UIPickerView & a Button in Action sheet - How?
In -viewDidLoad: you can't be sure that all IBOutlets are instantiated and all geometry set, so you better call your method in -view[Will/Did]Appear: if you're using any geometry transformations.
And it's easier to hide with hidden property
yourView.hidden = YES;

How to dynamically reposition UIButton as subview of UIImageView when rotating

I'm working on an iPad app that lets you control different things in a prototype of an intelligent house. For example it lets you turn lights on and off. For this, I have made a UIImageView that shows the floor plan of the house and added UIButtons as subviews for each lamp that can be toggled.
As you can see the buttons are placed perfectly on the floor plan using the setFrame method of each UIButton. However, when I rotate the iPad to portrait orientation, the following happens:
The buttons obviously still have the same origin, however it is not relative to the repositioning of the image.
The floor plan image has the following settings for struts and springs:
and has its content mode set to Aspect Fit.
My question is
how do I dynamically reposition each UIButton, such that it has the same relative position. I figure I have to handle this in the {did/should}AutorotateToInterfaceOrientation delegate method.
It should be noted that the UIImageView is zoomable and to handle this I have implemented the scrollViewDidZoom delegate method as follows:
for (UIView *button in _floorPlanImage.subviews) {
CGRect oldFrame = button.frame;
[button.layer setAnchorPoint:CGPointMake(0.5, 1)];
button.frame = oldFrame;
button.transform = CGAffineTransformMakeScale(1.0/scrollView.zoomScale, 1.0/scrollView.zoomScale);
}
Thank you in advance!
I find the best way to layout subviews is in the - (void) layoutSubviews method. You will have to subclass your UIImageView and override the method.
This method will automatically get called whenever your frame changes and also gets called the first time your view gets presented.
If you put all your layout code in this method, it prevents layout fragmentation and repetition, keeps your view code in your views, and most things just work by default.

Draw Over Image

I'm working on some drawing code. I have that portion working great.
I want to draw over an image, but I want to still be able to see the detail of the image, the black lines, etc.
What I am working on is making a transparent UIImageView that holds the image.
I'm not sure how to get this set up properly though.
Should this be added above the other UIImageView that I color on or below it?
Here's what I have so far:
- (void)viewDidLoad
{
[super viewDidLoad];
topImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 46, 320, 370)];
[topImageView setImage:[UIImage imageNamed:#"imagesmall.png"]];
topImageView.alpha = 1.0;
topImageView.layer.opacity = 1.0;
topImageView.layer.opaque = NO;
[self.view addSubview:topImageView];
[topImageView release];
}
Thoughts anyone?
Yes, you can draw views over other views. They are drawn in the order that they're added as subviews, unless you reorder them after that.
You may need to set the opaque property for some views (this is distinct from and overrides their layer opacity), and set their backgroundColor to nil. UIImageView seems to be transparent by default, as long as its image is; some other UIView subclasses are not.
So, just what is your overlay going to be? If you just need to display one image over another, what you have here seems to work already. If you need to draw some lines programmatically, you'll need to do this:
Create a subclass of UIView.
Implement its drawRect method to display the content you need.
When you add your custom view on top of the background image, make sure it is not opaque and has no backgroundColor.
A common problem here is to find that your foreground is working, but the background isn't being loaded properly. To make sure the background is there, set the alpha of the foreground view to 0.5. You won't want to do that in production, but it will allow you to verify that both views exist.