I'm trying to create a view where some items fall along normal gravity vector and others fall along an opposite vector
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self];
// Normal Gravity
self.gravityBehavior = [[UIGravityBehavior alloc] initWithItems:#[ ]];
self.gravityBehavior.gravityDirection = CGVectorMake(0, 1);
[self.animator addBehavior:self.gravityBehavior];
// Inverse Gravity
self.inverseGravityBehavior = [[UIGravityBehavior alloc] initWithItems:#[ ]];
self.inverseGravityBehavior.gravityDirection = CGVectorMake(0, -1);
[self.animator addBehavior:self.inverseGravityBehavior];
I would think then I could just add some items to one behavior and some to the other, but it appears that adding the second gravity behavior overrides the first?
[self.gravityBehavior addItem: ballA];
[self.inverseGravityBehavior addItem: ballB];
Is this true, and if so is there another way to achieve this effect?
This behavior is definitely possible. However, it cannot be achieved with one animator using differing UIGravityBehaviors.
One solution would be to use two UIDynamicAnimator instances, each with its own UIGravityBehavior instance. Below is an example of a view controller that has two tap gesture recognizers attached, one for single tap, and one for double. The single will add a view that reacts to normal gravity. The double tap adds a view that reacts to inverted gravity. Obviously the code would need to be tweaked to match your specific requirements.
#import "ViewController.h"
#interface ViewController ()
#property (nonatomic, strong) UIDynamicAnimator *animator;
#property (nonatomic, strong) UIDynamicAnimator *secondAnimator;
#property (nonatomic, strong) UIGravityBehavior *normalGravity;
#property (nonatomic, strong) UIGravityBehavior *inverseGravity;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
self.normalGravity = [[UIGravityBehavior alloc] init];
[self.animator addBehavior:self.normalGravity];
self.secondAnimator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
self.inverseGravity = [[UIGravityBehavior alloc] init];
[self.inverseGravity setAngle:self.normalGravity.angle magnitude:(-1.0 * self.normalGravity.magnitude)];
[self.secondAnimator addBehavior:self.inverseGravity];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)viewWasTapped:(id)sender {
UIView *v = [[UIView alloc] initWithFrame:CGRectMake(30, 10, 25, 25)];
v.backgroundColor = [UIColor redColor];
[self.view addSubview:v];
[self.normalGravity addItem:v];
}
- (IBAction)viewWasDoubleTapped:(id)sender {
UIView *v = [[UIView alloc] initWithFrame:CGRectMake(90, 400, 25, 25)];
v.backgroundColor = [UIColor greenColor];
[self.view addSubview:v];
[self.inverseGravity addItem:v];
}
#end
A second option would be to use a UIPushBehavior to simulate inverted gravity. Below is a similarly set up view controller, but without the second animator and using the push behavior.
#import "ViewController.h"
#interface ViewController ()
#property (nonatomic, strong) UIDynamicAnimator *animator;
#property (nonatomic, strong) UIGravityBehavior *normalGravity;
#property (nonatomic, strong) UIPushBehavior *pushBehavior;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
self.normalGravity = [[UIGravityBehavior alloc] init];
[self.animator addBehavior:self.normalGravity];
self.pushBehavior = [[UIPushBehavior alloc] initWithItems:#[] mode:UIPushBehaviorModeContinuous];
[self.pushBehavior setAngle:self.normalGravity.angle magnitude:(-1.0 * self.normalGravity.magnitude)];
[self.animator addBehavior:self.pushBehavior];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)viewWasTapped:(id)sender {
UIView *v = [[UIView alloc] initWithFrame:CGRectMake(30, 10, 25, 25)];
v.backgroundColor = [UIColor redColor];
[self.view addSubview:v];
[self.normalGravity addItem:v];
}
- (IBAction)viewWasDoubleTapped:(id)sender {
UIView *v = [[UIView alloc] initWithFrame:CGRectMake(90, 400, 25, 25)];
v.backgroundColor = [UIColor greenColor];
[self.view addSubview:v];
[self.pushBehavior addItem:v];
}
#end
In all situations be sure you are managing which items are being impacted by the UIDynamicAnimator and the various behaviors so that you are not constantly adding to them and bogging the simulation down.
Related
I have been searching for hours ... Needless to say, I'm rather new in programming.
My goal is to display a subview programmatically and to remove it by tapping on it.
Already working: Displaying the subview and removing it by tapping on the view (code below).
Doesn't work - whatever I try: Removing the subview by tapping exclusively on the subview.
Thanks for your help!
My .h-file:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController {
UIImageView *canvas;
UIImageView *square;
}
#property (nonatomic, strong) UIImageView *canvas;
#property (nonatomic, strong) UIImageView *square;
#property (nonatomic) NSUInteger numberOfTouchesRequired;
- (void)handleSingleTap: (UITapGestureRecognizer *)singleTapGestureRecognizer;
#end
My .m-file:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize canvas;
#synthesize square;
- (void)handleSingleTap: (UITapGestureRecognizer *)singleTapGestureRecognizer {
NSLog(#"tapGesture");
[square removeFromSuperview];
}
- (void)loadView {
canvas = [[UIImageView alloc] initWithFrame: [[UIScreen mainScreen] applicationFrame]];
[canvas setImage:[UIImage imageNamed:#"canvas.png"]];
self.view = canvas;
square = [[UIImageView alloc] initWithFrame:CGRectMake(170, 320, 40, 40)];
[square setImage:[UIImage imageNamed:#"square.png"]];
[self.view setUserInteractionEnabled:YES];
[canvas addSubview:square];
NSLog(#"square");
}
- (void)viewDidLoad {
[super viewDidLoad];
UITapGestureRecognizer *singleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)];
NSLog(#"initialize");
singleTapGestureRecognizer.numberOfTapsRequired = 1;
NSLog(#"number of taps");
[self.view addGestureRecognizer:singleTapGestureRecognizer];
NSLog(#"square gestureRecognizer");
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
If you want to remove a subview exclusively, when you tap on that view, then you will need to add tap gesture on that view
[square addGestureRecognizer:singleTapGestureRecognizer];
rather than
[self.view addGestureRecognizer:singleTapGestureRecognizer];
Edit
Also you will need to setUserInteractionEnabled to YES of UIImage because UIImage's setUserInteractionEnabled is default to NO.
[square setUserInteractionEnabled:YES];
Instead of
[self.view addGestureRecognizer:singleTapGestureRecognizer];
you should say
[square addGestureRecognizer:singleTapGestureRecognizer];
I have a UIVIew with Button. This is loaded in the VIewController as hidden. No problem unhiding it but the button on top of it is not working. Is the UIview class the right place to put the codes for the exit button (hiding it again)?
in .h
#interface infoView : UIView
#property (nonatomic, strong) IBOutlet UIView *infoTextView;
#property (nonatomic, strong) UIButton *exitButton;
-(void)exitView:(UIButton*)sender;
#end
in .m
#import "infoView.h"
#implementation infoView
#synthesize infoTextView = _infoTextView;
#synthesize exitButton=_exitButton;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
_infoTextView = [[UIView alloc]initWithFrame:CGRectMake(40, 40, 240, 240)];
[_infoTextView setBackgroundColor: [UIColor blueColor]];
[self addSubview:_infoTextView];
_exitButton = [[UIButton alloc] initWithFrame:CGRectMake(200, 10, 30, 30)];
//other info
[self.infoTextView addSubview:self.exitButton];
}
return self;
}
-(void)exitView:(UIButton*)sender
{
self.infoTextView.hidden=YES;
}
viewController
#implementation gjViewController
#synthesize imageViewMain = _imageViewMain;
#synthesize changeImageButton = _changeImageButton;
#synthesize infoButton = _infoButton;
#synthesize myInfo = _myInfo;
- (void)viewDidLoad
{
[super viewDidLoad];
_imageViewMain = [[UIImageView alloc] init];
//other info
[self.view addSubview:self.imageViewMain];
_changeImageButton = [[UIButton alloc] initWithFrame:CGRectMake(110, 50, 100, 50)];
//other info
[self.view addSubview:self.changeImageButton];
_infoButton = [[UIButton alloc] initWithFrame:CGRectMake(260, 30, 40, 40)];
//other details
[self.view addSubview: self.infoButton];
the section below is the part where the UIView is loaded. It loads and unhides with the IBAction at the bottom. I think you are pointing that this is wrong, not sure how. Thanks for comments.
_myInfo = [[infoView alloc] init];
[self.view addSubview:_myInfo];
[_myInfo setHidden:YES];
}
-(IBAction)newImage:(UIButton*)sender{
//task
-(IBAction)oldImage:(UIButton*)sender{
//taks
-(IBAction)viewInfo:(UIButton*)sender{ <--- unhides the UIView
[_myInfo setHidden:NO];
I have a problem with one of my views retaining its subviews. The main view displays 'tables' in a restaurant, and loads a subview to display this 'table'.
When the main view is deallocated, the tables seem to remain allocated to memory. I have searched everywhere to try and find a solution to this as I just can't seem to fix it myself.
Firstly the code for the 'table' view:
#protocol tableDelegate <NSObject>
#required
- (void)tablePress:(id)sender;
- (void)tableSelect:(id)sender;
#end
#interface tablevw : UIView
{
CGPoint currentPoint;
}
-(void)changeOrderImage;
-(id)initWithFrame:(CGRect)frame teststring:(NSString *)ts;
#property (nonatomic, weak) IBOutlet UIView *view;
#property (nonatomic, weak) IBOutlet UILabel *numberLabel;
#property (nonatomic, weak) IBOutlet UILabel *nameLabel;
#property (nonatomic) BOOL isEdit;
#property (nonatomic) BOOL hasOrder;
#property (nonatomic, strong) NSMutableDictionary * orderNumbers;
#property (nonatomic) int orderNumber;
#property (nonatomic, strong) NSString *teststring;
#property (nonatomic, weak) IBOutlet UIImageView *tableImage;
#property (nonatomic, weak) id<tableDelegate> delegate;
#end
And the init method for the view:
- (id)initWithFrame:(CGRect)frame teststring:(NSString *)ts{
self = [super initWithFrame:frame];
if (self) {
orderNumbers = [[NSMutableDictionary alloc]init];
isRemote = FALSE;
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"tabletopRound" ofType:#"png"];
UIImageView * iV =[[UIImageView alloc] initWithImage:[[UIImage alloc] initWithContentsOfFile:filePath]];
self.tableImage= iV;
tableImage.frame = CGRectMake(0, 0, 121, 121);
locked = FALSE;
[self addSubview:tableImage];
UITapGestureRecognizer *doubleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(doubleTap)];
doubleTapGestureRecognizer.numberOfTapsRequired = 2;
[self addGestureRecognizer:doubleTapGestureRecognizer];
UILabel * label = [[UILabel alloc] initWithFrame:CGRectMake(35, 50, 48, 20)];
numberLabel = label;
[numberLabel setTextColor:[UIColor blackColor]];
[numberLabel setBackgroundColor:[UIColor clearColor]];
[numberLabel setText:ts];
[self addSubview:numberLabel];
UILabel * labelTwo = [[UILabel alloc] initWithFrame:CGRectMake(24, 123, 70, 20)];
nameLabel = labelTwo;
[nameLabel setTextColor:[UIColor whiteColor]];
[nameLabel setBackgroundColor:[UIColor clearColor]];
[self addSubview:nameLabel];
self.userInteractionEnabled = YES;
self.tag = [ts intValue];
}
return self;
}
Finally, in the 'main' view the tables are added like so:
NSString *myString = [results stringForColumn:#"table_number"];
tablevw * tableView = [[tablevw alloc] initWithFrame:CGRectMake(x-60.5, y-60.5, 121, 121) teststring:myString];
tableView.delegate = self;
[self.view addSubview: tableView];
The delegate is set to Nil when the main view is dealloced. I have over ridden the dealloc method to log the dealloc calls to me - it is being called on the 'main' view but not on the 'table' view.
Thanks for your help
In the dealloc method try
for (UIView *view in [self.view subviews]) {
[view removeFromSuperview];
}
It would be even better if u can try
[tablevw removeFromSuperview], tablevw = nil;
I can see that your tablevw has a week reference to his delegate and that should be enough for to release the parent, but just for argue sake trie to nil tablevws delegate. Create an array of tablevw instances you created (or tag them as already suggested), then before you release parent remove them from superview, set their delegate to nil and kill the array. See what happenes, maybe that will help...
I'm using UIDynamics in my app. my app has two squares. one is fixed and the other can be moved (by applying a pan gesture). when i try to collide them they don't collide. the delegate methods never get called. here's my code and i hope someone can point out the problem.
UIDynamicAnimator* animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
UICollisionBehavior* collisionBehavior = [[UICollisionBehavior alloc] initWithItems:#[self.square1, self.square2]];
collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[self.square1 addGestureRecognizer:pan];
[collisionBehavior setCollisionMode:UICollisionBehaviorModeEverything];
[animator addBehavior:collisionBehavior];
collisionBehavior.collisionDelegate = self;
Make animator as a property or instance variable. otherwise it will
be cleared after view load.
Make sure implemented UICollisionBehaviorDelegate protol and correspond methods
Here is my works implementation based on your code.
Interface:
#import <UIKit/UIKit.h>
#interface DynamicViewController : UIViewController<UICollisionBehaviorDelegate>
#property (strong, nonatomic) UIDynamicAnimator *animator;
#property (strong, nonatomic) UIPushBehavior *pushBehavior;
#end
Implementation:
#synthesize pushBehavior = _pushBehavior;
#synthesize animator = _animator;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *square1 = [[UIView alloc] initWithFrame:CGRectMake(100, 30, 30, 30)];
square1.center = CGPointMake(150, 150);
square1.backgroundColor = [UIColor greenColor];
UIView *square2 = [[UIView alloc] initWithFrame:CGRectMake(100, 300, 30, 30)];
square2.center = CGPointMake(250, 350);
square2.backgroundColor = [UIColor redColor];
[self.view addSubview:square1];
[self.view addSubview:square2];
_animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
UICollisionBehavior* collisionBehavior = [[UICollisionBehavior alloc] initWithItems:#[square1, square2]];
collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;
[collisionBehavior setCollisionMode:UICollisionBehaviorModeEverything];
collisionBehavior.collisionDelegate = self;
[_animator addBehavior:collisionBehavior];
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[square1 addGestureRecognizer:pan];
}
#pragma mark push behavior
- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
if(gesture.state == UIGestureRecognizerStateBegan){
if (_pushBehavior) {
[_animator removeBehavior:_pushBehavior];
}
_pushBehavior = [[UIPushBehavior alloc] initWithItems:#[gesture.view] mode:UIPushBehaviorModeContinuous];
[_animator addBehavior:_pushBehavior];
}
CGPoint offset =[gesture translationInView:self.view];
_pushBehavior.pushDirection = CGVectorMake(offset.x,offset.y);
_pushBehavior.magnitude = sqrtf(offset.x * offset.x + offset.y * offset.y) / 50;
if (gesture.state == UIGestureRecognizerStateEnded) {
[_animator removeBehavior:_pushBehavior];
_pushBehavior = nil;
}
}
#pragma mark Colision delegate
- (void)collisionBehavior:(UICollisionBehavior*)behavior beganContactForItem:(id <UIDynamicItem>)item1 withItem:(id <UIDynamicItem>)item2 atPoint:(CGPoint)p;
{
NSLog(#"Colision Began");
}
- (void)collisionBehavior:(UICollisionBehavior*)behavior endedContactForItem:(id <UIDynamicItem>)item1 withItem:(id <UIDynamicItem>)item2;
{
NSLog(#"Colision End");
}
Your UIDynamicAnimator* animator is destroyed when function ends due to ARC. Thats why you are not able to see any animations. Make it a instance variable and you will be fine.
I think there is some bug with UIDynamics (but not 100% sure let me know if I'm wrong).
I couldn't make my dynamic works until I created instance variables for UIDynamicAnimator and UIGravityBehavior.
Try add instance variable to the .m file in class extension as:
UIDynamicAnimator* animator;
UICollisionBehavior* collisionBehavior;
And change your first two line of code to:
animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
collisionBehavior = [[UICollisionBehavior alloc] initWithItems:#[self.square1, self.square2]];
It should help.
You have to specify the bounds using "addBoundaryWithIdentifier" for the collision class and remove the non-moving item form the collision
Okay so i started learning UIDynamics and wanted to just work it out on small box.
I declared UIGravityBehaviour and UIDynamicsBehaviour in my ViewController.m and coded the UIView, but when i executed the code, it was not working.
But just as i declared them as #property in my .h file, the program was working completely fine.
What is the reason behind that?
What is the difference between declaring it as #property and without declaring it as #property?
Here are my files.
Before using #property
ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIView* box = [[UIView alloc]initWithFrame:CGRectMake(100, 50, 100, 100)];
box.backgroundColor = [UIColor blackColor];
[self.view addSubview:box];
UIDynamicAnimator* myAnimator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
UIGravityBehavior* gravityBehaviour = [[UIGravityBehavior alloc] initWithItems:#[box]];
[myAnimator addBehavior: gravityBehaviour];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
After Declaring it as #property
ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (strong,nonatomic) UIDynamicAnimator* myAnimator ;
#property (strong,nonatomic) UIGravityBehavior *gravityBehavior;
#property (strong,nonatomic) UICollisionBehavior *collisionBehavior ;
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIView* box = [[UIView alloc]initWithFrame:CGRectMake(100, 50, 100, 100)];
box.backgroundColor = [UIColor blackColor];
[self.view addSubview:box];
self.myAnimator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
self.gravityBehavior = [[UIGravityBehavior alloc] initWithItems:#[box]];
self.collisionBehavior = [[UICollisionBehavior alloc] initWithItems:#[box]];
self.collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;
[self.myAnimator addBehavior:self.gravityBehavior];
[self.myAnimator addBehavior:self.collisionBehavior];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
You don't have to move UIGravityBehavior to a property, only UIDynamicAnimator to make it work. However, it's probably a good idea anyway in case you want to remove specific gravity behaviours rather than all of them at once.
If you are using ARC:
The UIDynamicAnimator probably does not have a strong reference to the UIGravityBehavior. So after your viewDidLoad method returns, no one owns that UIGravityBehavior and ARC will make sure that that point it is released. When you make a property for it, your viewController owns it, so the UIGravityBehavior will survive until you viewController gets deallocated.