Why arent the views being added to this UIAnimator? - ios

Im adding views to an animator like so:
-(void)drawCellLikeViews{
//2. Add Animator
_animator = [[UIDynamicAnimator alloc] initWithReferenceView:self];
//Alloc array
viewsArray = [[NSMutableArray alloc] initWithCapacity:6];
for (int numberOfViews = 0; numberOfViews < 5; numberOfViews++) {
//Create views
UIView* cell = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 70)];
cell.backgroundColor = [UIColor grayColor];
[self addSubview:cell];
[viewsArray addObject:cell];
}
//3. Add Gravity
_gravity = [[UIGravityBehavior alloc] initWithItems:viewsArray];
[_animator addBehavior:_gravity];
//4. Add boundaries
_collision = [[UICollisionBehavior alloc] initWithItems:viewsArray];
_collision.translatesReferenceBoundsIntoBoundary = YES;
[_animator addBehavior:_collision];
//8. Add ItemProperties' Behaviors
UIDynamicItemBehavior* itemBehaviour = [[UIDynamicItemBehavior alloc] initWithItems:viewsArray];
itemBehaviour.elasticity = 0.6;
//9. itemBehaviour.action = ^{};
[_animator addBehavior:itemBehaviour];
NSLog(#"viewcount %d", [[self subviews] count]);
}
I only see one drop down to the bottom.

#HalR is correct—your views are being added to the animator; they're just all being added at the same time with the same starting attributes, so they will all behave in exactly the same way and appear as one item. And yes, by default, the collision behavior's mode is UICollisionBehaviorModeEverything, meaning its items should collide with each other and with the boundaries. Yet, by implying UICollisionBehaviorModeEverything and placing all of the items on top of each other, you've created an impossible scenario wherein you've explicitly told the edges of each item to overlap, but you've told the animator that they're not allowed to overlap. The animator's behavior at that point is undefined—I think it just ignores whatever physics rule you've manually broken, but it can get much weirder than that. In WWDC 2013 Session 206 (Getting Started with UIKit Dynamics), Apple at one point says "don't expect the impossible" saying that "you can create setups which don't have solutions" and showing overlapping items with collision behaviors set as an example. In other words, they saw this coming, and they just don't want you to do it. ;)
The way to make this work, of course, would just be to give each of the items a starting position that doesn't overlap with other items' starting positions, or to add them all to where you're adding them now, only a few seconds apart from each other so the last one can fall out of the way.

Related

Is UICollectionView.backgroundView broken

I'm writing something relatively simple, or so I thought.
Firstly, the code, for which I'm trying to place an image on the background of the UICollectionView if there are no results returned from my server. The image is 200 by 200:
UIView *myView = [[UIView alloc] initWithFrame:self.view.bounds];
CGRect myViewSpace = self.view.bounds;
CGFloat myX = (myViewSpace.size.width /2.0) - 100;
CGFloat myY = (myViewSpace.size.height /2.0) - 100;
UIImageView *imView = [[UIImageView alloc] initWithFrame:CGRectMake(myX, myY, 200, 200)];
imView.image = [UIImage imageNamed:#"imNotHome"];
[myView addSubview:imView];
myCollectionView.backgroundView = myView;
Once there are results, I want to be able to remove it.
I thought it'd be as simple as placing the following, before I reloaded the UICollectionView:
[myCollectionView.backgroundView removeFromSuperview];
However, it appears to be doing nothing.
Am I missing something?
Thanks in advance!
It should be done this way instead:
myCollectionView.backgroundView = nil;
Explanation: You should unset the UICollectionView's background in the same way as you set it. You didn't set it by manipulating the view hierarchy, but by setting the background property. You did call addSubview in the previous line, but that was to add a subview to your background view, not to add the background view itself.
Edit:
There is a very good article about this UICollectionView bug here:
http://blog.spacemanlabs.com/2013/11/uicollectionviews-backgroundview-property-is-horribly-broken/
The solution the author gives is to reset the background to an empty opaque view:
UIView *blankView = [UIView new];
blankView.backgroundColor = [UIColor whiteColor];
[myCollectionView.backgroundView removeFromSuperview];
myCollectionView.backgroundView = blankView;
Also, the author recommends not using the backgroundView property at all but doing it yourself:
Frankly, I think the best solution is to just ignore the backgroundView property all together. Instead, make the collection view’s background clear, and implement your own backgroundView; just throw a view behind the collection view.

Moving a cell using CGRectOffset - what am I doing wrong?

I stumbled upon this fantastic library for implementing table view cell sliding, but there's a few minor differences between what it offers and what I'm looking to have, so I'm continuing with my implementation.
The way the developer moved the table view cells was creative, I thought, in that he used CGRectOffset() to add almost padding to the sides of the cell as it moved to look like it was moving.
I want to do something similar for mine, as it seems like the easiest way to accomplish this. However, when I do the following, nothing happens to the cell:
(Just an example):
CGFloat offset = 11;
self.contentView.frame = CGRectOffset(self.contentView.bounds, offset, 0);
I set a breakpoint there, so it's indeed being called when I pan across the cell (it's in the gesture recognizer method). Yet after it gets called, the cell looks identical in position.
And I have this at the beginning, similar to how he set up the view:
- (void)awakeFromNib {
[super awakeFromNib];
self.contentView.backgroundColor = [UIColor whiteColor];
UIView *backgroundView = [[UIView alloc] initWithFrame:self.contentView.frame];
backgroundView.backgroundColor = [UIColor whiteColor];
self.backgroundView = backgroundView;
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(pannedCell:)];
panGestureRecognizer.delegate = self;
[self addGestureRecognizer:panGestureRecognizer];
}
So I'm not sure exactly why it's not working. Does anyone have any ideas?

Programatically generated UILabel origin point incorrectly to (0.0) set on first load

I am trying to programatically generate two UILabels in my application for each UIImageView on my storyboard. The code runs and works correctly, however, on first load the two UILabels form in the (0.0) coordinate of the main view, as opposed to the UIImageView frame origin.x,origin.y. I can't understand why this is happening.
If I then click on a different tab and return to the page, the labels generate in the correct location.
Why is this? How can I get it to initially generate the labels in the correct location?
-(void) viewWillAppear:(BOOL)animated
{
//removed unneccessary code above...
int i = 0;
for (UIImageView *plantScreen in self.view.subviews)
{
if ([plantScreen isMemberOfClass:[Plant class]])
{
#try
{
//the label which will hold the name
UILabel *plantName = [[UILabel alloc] initWithFrame:CGRectMake((plantScreen.frame.origin.x), (plantScreen.frame.origin.y+ plantScreen.frame.size.height), 160, 30.0)];
plantName.numberOfLines = 1;
plantName.minimumScaleFactor = .5;
plantName.adjustsFontSizeToFitWidth = YES;
[plantName setTextAlignment:NSTextAlignmentCenter];
[plantName setBackgroundColor:[UIColor clearColor]];
[self.view addSubview:plantName];
plantName.hidden = false;
[self.view bringSubviewToFront:plantName];
//create the label which will hold the quantity
UILabel *quantity = [[UILabel alloc] initWithFrame:CGRectMake((plantScreen.frame.origin.x), (plantScreen.frame.origin.y+ plantScreen.frame.size.height + 20), 160, 30.0)];
[quantity setTextAlignment:NSTextAlignmentCenter];
[quantity setBackgroundColor:[UIColor clearColor]];
quantity.text = [NSString stringWithFormat:#"%d",plant.quantity];
[self.view addSubview:quantity];
quantity.hidden = false;
[self.view bringSubviewToFront:quantity];
i++;
}
#catch (NSException *exception)
{
NSLog(#"An exception occured: %#", [exception reason]);
}
#finally
{
}
}
}
}
Frame of the UIImageView depends on the image being drawn and its contentMode property. You can try setting the contentMode to UIViewContentModeScaleAspectFill to see if it forces to keep its assigned frame.
First things first, you're missing a call to [super viewWillAppear:animated]. You need to give the superclass (including the UIViewController base class) a chance to "do its magic".
Never forget about giving the parent class a chance to do its magic, unless you know really really well what you're doing.
Second, UI creation should be done in -loadView, not in viewWillAppear:.
Try these two things first.
Alright. Now I'm curious about how you moved things to -loadView. Did you add [super loadView];?
In fact, now that I think about it, moving to -loadView is wrong in this case; you obviously instantiate some views through a nib. UIViewController's implementation of -loadView typically just loads the nib file. Once that's done, UIViewController's -loadView calls -viewDidLoad.
So when you're not creating all UI programmatically but are instead allowing UIViewController to load it from nib, you actually probably want to move code into -viewDidLoad. (See template generated by Xcode when you tell it to create a new UIViewController subclass.)
Moving on, let's consider what the frame depends on. It depends on some view class you called Plant.
Please don't call it that way; it's confusing. Call it PlantView, so a casual reader of your code is aware of what the class is supposed to do. Similarly, you might want to call the variables plantView instead of plantScreen, and plantNameLabel instead of plantName. plantScreen implies a variable containing UIScreen, and plantName implies an NSString more than it implies a UILabel. Same applies to quantity; call this variable quantityLabel.
Next, let's consider what the variables are depending on -- their origin's x and y do not change based on the counter, variable i. Perhaps you meant to write:
UILabel *plantName = [[UILabel alloc] initWithFrame:CGRectMake((plantScreen.frame.origin.x), (plantScreen.frame.origin.y+ plantScreen.frame.size.height * i), 160, 30.0)];
and later on:
UILabel *quantity = [[UILabel alloc] initWithFrame:CGRectMake((plantScreen.frame.origin.x), (plantScreen.frame.origin.y+ plantScreen.frame.size.height*i + 20), 160, 30.0)];
Next, avoid exceptions and exception handling. Ensure that the exception does not occur via other forms of checking; Apple highly recommends you fix exceptions while writing the application and not handle them when they run:
Important: You should reserve the use of exceptions for programming or
unexpected runtime errors such as out-of-bounds collection access,
attempts to mutate immutable objects, sending an invalid message, and
losing the connection to the window server. You usually take care of
these sorts of errors with exceptions when an application is being
created rather than at runtime.
Next, a small stylistic remark (not very important): you're mixing calling setters via properties and calling setters directly. Nothing wrong (they end up doing exactly the same), but stylistically not very nice.
Next, unless you're using ARC (automatic reference counting), don't forget to release the views once they're added as subviews.
Next, plantScreen (which I named plantView below) can have type set to Plant (which I named PlantView below) when declared inside the loop.
Last but highly important and extremely easy to miss: you call the function isMemberOfClass: instead of isKindOfClass:.
Reworked version of your code (untested):
-(void)viewDidLoad
{
[super viewDidLoad];
int i = 0;
for (PlantView *plantView in self.view.subviews)
{
if ([plantView isKindOfClass:[PlantView class]])
{
//the label which will hold the name
CGRect plantNameLabelFrame = CGRectMake((plantScreenView.frame.origin.x),
(plantScreenView.frame.origin.y + plantScreen.frame.size.height*i),
160,
30.0);
UILabel *plantNameLabel = [[UILabel alloc] initWithFrame: plantNameLabelFrame];
plantNameLabel.numberOfLines = 1;
plantNameLabel.minimumScaleFactor = .5;
plantNameLabel.adjustsFontSizeToFitWidth = YES;
plantNameLabel.textAlignment = NSTextAlignmentCenter;
plantNameLabel.backgroundColor:[UIColor clearColor];
[self.view addSubview:plantNameLabel];
[plantNameLabel release];
//create the label which will hold the quantity
CGRect quantityLabelFrame = CGRectMake((plantScreenView.frame.origin.x),
(plantScreenView.frame.origin.y + plantScreen.frame.size.height*i + 20),
160,
30.0);
UILabel *quantityLabel = [[UILabel alloc] initWithFrame: quantityLabelFrame];
quantityLabel.textAlignment = NSTextAlignmentCenter;
quantityLabel.backgroundColor = [UIColor clearColor];
quantityLabel.text = [NSString stringWithFormat:#"%d", plant.quantity]; // NB: what's "plant"?
[self.view addSubview:quantityLabel];
i++;
}
}
}
I've also removed .hidden = false (which should actually read .hidden = NO; this is Objective-C, and not C++), and bringSubviewToFront: (it's already in front, having just been added by addSubview:).

How to update UILabel programmatically in iOS

I am facing a problem with updating my labels.. it doesn't remove the old values so new values go on top of old ones.. any help with this will be appreciated..
timer = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(updateLabels)
userInfo:nil
repeats:YES];
-(void) updateLabels{
for (GraphView *graph in arr){
// retrieve the graph values
valLabel = [[UILabel alloc] initWithFrame:CGRectMake(i * 200, 0, 90, 100)];
valLabel.textColor = [UIColor whiteColor];
valLabel.backgroundColor = [UIColor clearColor];
valLabel.text = [NSString stringWithFormat:#"Value: %f", x];
i++;
}
}
If you set the text of your label you do not need to call setNeedsDisplay or clearContext etc.
In your code, I do not know what are your variables i and x?
The main problem is that you are creating and adding new labels on your view. When you call updateLabels method, may cause a Memory leak. Simply you have n times labels overlapped.
You need to remove the labels before you create and add new labels or you can reuse which you already have. To reuse your labels you need to save them to an array and update texts.
If you want to create new labels then you can do like this unless you have other labels in your view
-(void) updateLabels{
// remove all labels in your view
for (UIView *labelView in self.view.subviews) {
if ([labelView isKindOfClass:[UILabel class]]) {
[labelView removeFromSuperview];
}
for (GraphView *graph in arr){
// retrieve the graph values
valLabel = [[UILabel alloc] initWithFrame:CGRectMake(i * 200, 0, 90, 100)];
valLabel.textColor = [UIColor whiteColor];
valLabel.backgroundColor = [UIColor clearColor];
valLabel.text = [NSString stringWithFormat:#"Value: %f", x];
i++;
}
}
When you create new labels like this you need to add them to your view as subview
[self.view addSubview: valLabel];
if you have other labels in your view then you can save them in an array and remove just them
Your updateLabels method is actually creating new UILabel controls each time so they will simply appear "on top of" older ones. I'm guessing this is not what you want, although it's not perfectly clear so apologies if I've misunderstood what you're trying to do.
If I'm correct about that, create your UILabel controls just once maybe in your viewDidLoad or similar. Then just set their .text properties when your timer fires.
You need to call setNeedsDisplay so that the app knows it has changed and redraw it.
- (void)setNeedsDisplay
Set clearsContextBeforeDrawing property of your label to YES
you can set this from nib as well as code.
label.clearsContextBeforeDrawing = YES;

UIActionSheet with an Image

For God's sake somebody tell me how to add a picture on an UIActionSheet.
I am adding it, but can't force the sheet to restretch its height, so the Subview would fit.
var sheet = new UIActionSheet ("Sheet with a picture");
sheet.AddButton ("Pick New Picture");
var subView = new UIView(new RectangleF(20,100,280,200)){
BackgroundColor = UIColor.FromPatternImage(Picture)};
sheet.AddSubview(subView);
sheet.AddButton ("Cancel");
sheet.CancelButtonIndex = 1;
I've tried to change contentMode of subView and the sheet. Didn't work. What am I doing wrong?
Picture should fit between buttons, or fit on the sheet somehow through any other way around
I know this sounds really stupid, but the easiest solution I found is to add a few dummy buttons (to preserve space) and then on top of them add the UIImageView accurately defining the frame coordinates.
var sheet = new UIActionSheet ("");
sheet.AddButton ("Discard Picture");
sheet.AddButton ("Pick New Picture");
sheet.AddButton ("Cancel");
sheet.CancelButtonIndex = 2;
// Dummy buttons to preserve the space for the UIImageView
for (int i = 0; i < 4; i++) {
sheet.AddButton("");
sheet.Subviews[i+4].Alpha = 0; // And of course it's better to hide them
}
var subView = new UIImageView(Picture);
subView.ContentMode = UIViewContentMode.ScaleAspectFill;
subView.Frame = new RectangleF(23,185,275,210);
// Late Steve Jobs loved rounded corners. Let's have some respect for him
subView.Layer.CornerRadius = 10;
subView.Layer.MasksToBounds = true;
subView.Layer.BorderColor = UIColor.Black.CGColor;
sheet.AddSubview(subView);
UIActionSheet doesn't support customization of this type. You can subclass UIActionSheet and muck with the default layout (override layoutSubviews, call the super implementation, then move things around). If you do this, there's no guarantee your sublcass will work in future versions of iOS if Apple changes the framework implementation.
The other alternative is to find or implement an alternative class that does what you want.
Actually it is quite easy and you don't need a hack:
Change the size of the UIActionSheet AFTER calling showInView (or showFromTabBar, etc), like they do in this example.
You might have to change the Frame instead of the Bounds, in my experience the action sheet is not moved to the right position if you change the Bounds.
On the iPad this doesn't work unfortunately. But there you can use a UIPopoverController. (Tip: you can use 0 for the UIPopoverArrowDirection if you do not want arrows).
This is Agzam's answer, but refreshed for most recent objc and without the background button hack, as suggested by Marcel W. it is shorter and possibly cleaner.
UIActionSheet *actionSheet = [[UIActionSheet alloc] init];
actionSheet.actionSheetStyle = UIActionSheetStyleAutomatic;
actionSheet.title = #"some text";//here type your information text
actionSheet.delegate = self;
[actionSheet addButtonWithTitle:#"Button 1"];
[actionSheet addButtonWithTitle:#"Button 2"];
[actionSheet setCancelButtonIndex:1];
//it is up to you how you show it, I like the tabbar
[actionSheet showFromTabBar:self.tabBarController.tabBar];
//here lays the trick - you change the size after you've called show
[actionSheet setBounds:CGRectMake(0,0,320, 480)];
//now create and add your image
UIImageView *subView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"exampleimage.png"]];
subView.ContentMode = UIViewContentModeScaleAspectFit;
subView.Frame = CGRectMake(0,130,320,180);//adjust these, so that your text fits in
[actionSheet addSubview: (subView)];
[subView release];
[actionSheet release];

Resources