One concern over a universal app I currently developing.
I just finishing with functionality development and need to add eye candy (animations).
Now wondering in a single iPhone or iPad app I would just play and set frame, bounds, to move a View, layer etc with hardcoded values.
Now how would you handled the different device frame and bounds?
Meaning you want to move a UIImageView from outside the parent View bounds and make it come to a position. Handling differently while testing what device we running at?
Thank you.
You could add a method in your AppDelegate,
- (BOOL) ISIPAD {
return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad);
}
And refer to that each time you are making animations. If you then have some variables in your class with the width of the screen and so, you only need to check for this in either the viewDidLoad, or your init method. Then you could use the dynamic variables to do your animations. To place a button in the middle of the screen, you could do:
[UIView animateWithDuration:1.0 animations:^{
UIButton *yourButton;
yourButton.frame = (CGRect){.origin = CGPointMake(screenwidth/2.0f-yourButton.frame.size.width/2.0f, screenheight/2.0f - yourButton.frame.size.height/2.0f), .size = yourButton.frame.size};
}];
Considering the screenvariables are set in your init or viewDidLoad method, this will put your button in the middle of your screen.
Related
i am calling setFrames multiple times for the same view.
for example after setting ltr views frames, i check if the layout should be rtl and change the views frames again.
-(void)setViewsFrames:(BOOL)RTL{
view1.frame = CGRECMAKE();
view2.frame = CGRECMAKE();
view3.frame = CGRECMAKE();
.
.
.
if(RTL){
[view1 setRtlFrame];
[view2 setRtlFrame];
[view3 setRtlFrame];
.
.
.
}
}
-(void)setRtlFrame{
CGRect RTLFrame = self.frame;
RTLFrame.origin.x = [self superview].frame.size.width - self.frame.origin.x - self.frame.size.width;
[self setFrame:RTLFrame];
}
does calling setFrames multiple time force the system to draw the view multiple times ? and may that effect performance.
I am using that also in UICollectionViewCell, so the system calls setViewsFrames: every time she want to draw the cell.
EDIT:
i've did a small test. i check when drawRect is called and here is the result:
it's called just one time, no matter how much times setFrame was called.
in UICollectionCellView it called just when the cell created or at reload.
setting the frame calls 'setNeedsLayout' and then on the next runloop iteration, IOS knows to layout & redraw the view.
there'd be no point in layouting / redrawing stuff the user doesn't see so iOS coalesces the calls for you -- if you let it by using the setNeedsXY methods
so the overhead of setting the frame is normally minimal
(except if you deal with custom (badly implemented) views [which you don't ;)])
I am a beginner developing an iOS game using sprite kit. I would like to have 'pop-up' menu's that display objects (skspritenodes) such as daily rewards or settings/pause menu. The settings pop-up for example should be able to be accessed on the main home screen or during the game. Currently I have one view controller and the home scene and play scene are two different SKScenes. For reusability (and a clean project, as well as learning) I would like to have these pop-ups be their own class, including handling touches. Currently, I have a class for the settings pop-up that returns an SKNode, and this sknode contains several skspritenodes (the buttons/images that correspond to a settings pop up including enabling/disabling sound etc). However, I have to duplicate the touches code in both my home scene and my play scene to interact with this. (I currently have, in each skscene's touchesbegan method, "if nodeSettings !=nil", check the name of the skspritenode that corresponds to the touch-location, then call a method in the setting-pop-up class passing the name of the skspritenode clicked to handle interactions with the pop-up).
For my own knowledge and also to solve this problem, I would like to use a class that can handle the touch logic on its own (so in my play or home skscene, the only thing I do is create the pop-up. Any interaction, including dismissal, I would like to have handled in the class. No using the skscene's touches methods). I have found one 'solution' to this:
LTPopUpReward *test = [[LTPopUpReward alloc] init];
UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:test];
CGFloat fltHeight = [UIScreen mainScreen].bounds.size.height;
CGFloat fltWidth = [UIScreen mainScreen].bounds.size.width;
popover.popoverContentSize = CGSizeMake(fltWidth/2, fltHeight/2); //your custom size.
CGRect rectPopUpReward = CGRectMake(500, 500, fltWidth/2, fltHeight/2);
[popover presentPopoverFromRect:rectPopUpReward inView:self.view permittedArrowDirections: UIPopoverArrowDirectionAny animated:YES];
and I like that you can dismiss the UIPopoverController by clicking outside of it, but this UIPopoverController only works on the iPad and doesn't work on the iPhone. I've seen this UIPopoverController for iphone not working? but was wondering if there is another way to solve my problem that Apple won't possibly disapprove of? For example, could I create a UIViewController, resize it and position it (i don't know how to do this) to simulate the pop-up?
Summarizing: my goal is to have a pop-up appear, but have all of its code (including touch) self contained within one class, and the objects on the pop-up are skspritenodes.
answering my own question: The solution I used to solve this problem is to use different SKViews. One SKView presents the main scene and another SKView presents the pop-up scene. You can adjust the size of the second SKView and even make its background transparent. Interacting with objects in this view will correctly call the touches methods in the class of the scene presented - That is this pop-up is dealt with when being created, and there is no other code dealing with it on the view-controller or main-scene's logic (all code, including creating objects and handling touch) are in the second skscene's code. The method below is called when I do "something" to call the 'pop-up'.
-(void)test4
{
skViewPopUp=nil;
scenePopUpReward=nil;
NSLog(#"test4 successfully fired");
CGFloat fltHeight = [UIScreen mainScreen].bounds.size.height;
CGFloat fltWidth = [UIScreen mainScreen].bounds.size.width;
CGRect rectPopUpReward = CGRectMake(0, 0, fltWidth/2, fltHeight/2);
skViewPopUp = [[SKView alloc] initWithFrame:rectPopUpReward];
[self.view addSubview:skViewPopUp];
skViewPopUp.allowsTransparency = YES;
scenePopUpReward = [[LTSceneStoreMain alloc] initWithSize:CGSizeMake(fltWidth/2, fltHeight/2)];
scenePopUpReward.backgroundColor = [UIColor clearColor];
[skViewPopUp presentScene:scenePopUpReward];
}
I found some questions and answers here on stackoverflow for that problem, but none of the solutions there solved my problem.
My iOS App has the ability to play some music with a nice music player. I designed it with Xcode's Interface Builder and dragged out a UIView and changed its class to MPVolumeView. Everything works fine when I'm debugging my app on my iPhone 6.
Here is my problem: I also dragged out a UITapGestureRecognizer on my whole view which contains my controls like
play/pause, next/previous track (...)
and also my MPVolumeView. When I tap on that view it should fade out and disappear. Then I added a UITapGestureRecognizer on my UIImageView which shows my artwork image of the song. When I tap this image view, it should fade in my view with all controls in int - that's working properly.
BUT: When I slide the knob of the volume slider just a little bit, or if I am just touching it, the view still disappears. It seems like my MPVolumeView is forwarding my touch or something like that. I tried setting userInteractionEnabled = false on my volume slider, but that didn't help. I also set the delegate of my gesture recognizer to self and added the
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
NSLog(#"tapped");
if([gestureRecognizer.view isMemberOfClass:[UIImageView class]]) {
return true;
}
return false;
}
function to my code, which returns true or false, depending on which view I'm tapping. When I'm accessing the gestureRecognizer.view property, it doesn't recognize my MPVolumeView, just the UIView in the background.
Here my two methods which are fired after when the TapGestureRecognizers are fired:
- (IBAction)overlayViewTapped:(UITapGestureRecognizer *)sender {
if(sender.state == UIGestureRecognizerStateEnded) {
[UIView animateWithDuration:0.3
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{ self.blackOverlayView.alpha = 0.0; self.normalTimeLabel.alpha = 1.0; }
completion:nil];
}
}
- (IBAction)imageViewTapped:(UITapGestureRecognizer *)sender {
[UIView animateWithDuration:0.3
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{ self.blackOverlayView.alpha = 1.0; self.normalTimeLabel.alpha = 0.0; }
completion:nil];
}
Please help me, I'm nearly going nuts with that ..
EDIT: My music player looks like this:
After I tap anywhere on the view (except the subviews), the view should fade out and hide everything, just show the artwork image of the song and the current elapsed time. This will look like this:
As I said - the problem is, if I just tap the volume slider or slide it just a little bit, my UITapGestureRecognizer fires and fades out my complete view. How can I prevent that?
It is behaving the way it is simply because you added the gesture recognizer to the entire UIView, which includes the volume slider and whatnot.
Instead of detecting the touch in the entire view, check to see if the touch is in the area you want it.
Create a CGRect property, I'll call it touchArea:
#property CGRect touchArea;
Then specify the size of the touchArea (you can do this in the viewDidLoad):
touchArea = CGRectMake(0.0, 240.0, 320.0, 240.0);
You will have to find out where you want this and how big it should be and replace my example values with the real ones. A simple way of cheating this is to take something like a UILabel in IB and positioning and sizing it to your desire, then go to the size inspector pane and get the x, y, width and height values.
Then, before you do your fade animation, check to see if the touch was in the touchArea:
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer
{
CGPoint touchPoint = [gestureRecognizer locationInView:self.view];
if (CGRectContainsPoint(touchArea, touchPoint))
{
//do your animation here.
}
}
As a note, I would set a BOOL to check whether or not the view is faded in or out, so you can always check before animating.
I have created a UIButton programmatically and horizontally centered it using the following code
button.center = CGPointMake(self.view.center.x, 50);
When device is rotated, it's no longer in the center. How can I fix this problem? Thanks in advance.
Kevin is correct, but a better solution would be to set the button's center in your view controller's willAnimateRotationToInterfaceOrientation:duration: method.
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration
{
button.center = CGPointMake(self.view.center.x, 50);
}
He uses the didRotateFromInterfaceOrientation:fromInterfaceOrientation method, which, as the Apple documentation states "this method might be used to reenable view interactions, start media playback again, or turn on expensive drawing or live updates."
In the case of laying out subviews, the willAnimateRotationToInterfaceOrientation:duration: will provide a smoother transition, as it is called from within the rotation's animation block.
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
button.center = CGPointMake(self.view.center.x, 50);
}
Apple is missing documentation on how to use UIPopoverBackgroundView class introduced in iOS5. Anyone have an example?
I have tried to subclass it, but my XCode 4.2 on Lion is missing UIPopoverBackgroundView.h
Edit: Unsurprisingly, it should have been imported as #import <UIKit/UIPopoverBackgroundView.h>
To add to the other, link-only answers, here is how this is done.
Create a new subclass of UIPopoverBackgroundView
Declare the following in your interface:
+(UIEdgeInsets)contentViewInsets;
+(CGFloat)arrowHeight;
+(CGFloat)arrowBase;
#property(nonatomic,readwrite) CGFloat arrowOffset;
#property(nonatomic,readwrite) UIPopoverArrowDirection arrowDirection;
The class methods are straightforward: contentViewInsets returns the width of your borders all the way round (not including the arrow), arrowHeight is the height of your arrow, arrowBase is the base of your arrow.
Implement the two property setters, making sure to call [self setNeedsLayout].
In your initialisation method, create two image views, one holding your arrow (which should be the size of the arrow dimensions in your class methods) and one holding your background image (which must be a resizable image) and add these as subviews. It doesn't matter where you put the subviews at this point, as you don't have an arrow direction or offset. You should make sure the arrow image view is above the background image view so it blends in properly.
Implement layoutSubviews. In here, according to the arrowDirection and arrowOffset properties, you have to adjust the frames of your background view and arrow view.
The frame of your background view should be self.bounds, inset by arrowHeight on whatever edge the arrow is on
The frame of the arrow view should be aligned so that the centre is arrowOffset away from the centre of self (correct according to the axis). You have to change the image orientation if the arrow direction is not up, but my popover would only be up so I didn't do that.
Here is the layoutSubviews method for my Up-only subclass:
-(void)layoutSubviews
{
if (self.arrowDirection == UIPopoverArrowDirectionUp)
{
CGFloat height = [[self class] arrowHeight];
CGFloat base = [[self class] arrowBase];
self.background.frame = CGRectMake(0, height, self.frame.size.width, self.frame.size.height - height);
self.arrow.frame = CGRectMake(self.frame.size.width * 0.5 + self.arrowOffset - base * 0.5, 1.0, base, height);
[self bringSubviewToFront:self.arrow];
}
}
Another link only answer, but customising UIPopoverBackgroundView is more work than you might realise given the limited documentation available and this github project has a complete working example which saved me a lot of time: https://github.com/GiK/GIKPopoverBackgroundView
It's fairly straightforward to drop into your own project. The most fiddly part is adapting the cap insets for whatever custom images you're using. I'd recommend doing your customisations in-situ in the project and as it's easy to verify all the popover orientation/direction use cases display correctly in the simulator before migrating it into your own project.