iOS UIKit Dynamics - unable to add bouncy behaviour for a view - ios

I am trying to add a bouncy behaviour (collision detection) to view which contains all the contents like buttons and images named as ViewContent [it should fall from top and collide/bounce with the bottom of screen].
Here is my view Hierarchy:
and Code:
viewController.h
#interface LoginViewController : UIViewController <UIScrollViewDelegate, UICollisionBehaviorDelegate>
#property (weak, nonatomic) IBOutlet UIView *viewContent;
#property (nonatomic, strong) UIDynamicAnimator *animator;
#end
in storyboard I've set the viewContent frame as (0, -568, 320, 568) so that is should come from top
viewController.m
#implementation LoginViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:#[viewContent]];
gravityBehavior.gravityDirection = CGVectorMake(0.0, 1.0);
[self.animator addBehavior:gravityBehavior];
UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc] initWithItems:#[viewContent]];
collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;
collisionBehavior.collisionDelegate = self;
[self.animator addBehavior:collisionBehavior];
UIDynamicItemBehavior *contentViewBehavior = [[UIDynamicItemBehavior alloc] initWithItems:#[viewContent]];
contentViewBehavior.elasticity = 0.4;
[self.animator addBehavior:contentViewBehavior];
As a result nothing happens. I'm following this tutorial http://www.teehanlax.com/blog/introduction-to-uikit-dynamics/ and able to add a collision detection on a square with frame (100, 100, 100, 100), but not able to add collision on a view having frame (0, 0, 320, 568).
I'm new and can't figure it out, any ideas/suggestion please.
Update
Nib view added as #Fogmeister suggested.
I've added a separate nib view file named as LoginView and assigned the LoginViewController class so that I am able to add IBOutlets and IBActions from this Nib.
In LoginViewController's ViewDidLoad method just added these lines right under the [super viewDidLoad
self.content = [[[NSBundle mainBundle]
loadNibNamed:#"ViewLogin"
owner:self options:nil]
firstObject];
[self.view addSubview:self.content];
rest of the code is remain same except in initWithItems viewContent is just replaced with this newly created view self.content.

At the moment you can't use AutoLayout and UIKit Dynamics together. They just don't work together at all.
Also, the first rule of AutoLayout is that you can't update the frame of a view directly. So your line self.viewContent.frame = CGRectMake = (0, -568, 320, 568); won't work either.
What you need to do with this view is disable AutoLayout in order to use Dynamics.
Seeing as you are using a storyboard then the problem is that disabling auto layout for one view will disable it for all views.
If you just want the main view to bounce then what you can do is define that view in a separate nib (or in code).
Either way you then need to programmatically add that view to the view controller's view.
Then your dynamics will work.

Related

How to put text on left and right side of UIButton

I am new to iOS development and I recently got stucked with a problem where I am not able to set text on both sides of a button. Also I wish to add multi-line text in a button (as shown below). I have gone through lot of answers but none of them satisfied my requirement.
Any suggestion or help would greatly be appreciated, thanks!
You should definitely go with a custom UIButton class. The reason for this is that UIButton has other properties that you will want, like being recognized by iOS as a button, conform to Accessibility as a button, show up in storyboard as a button class, let your co-workers see that it actually is a button, have same/similar interface as a button, and so on.
Creating a custom button isn't that hard. Basically, just sub-class UIButton and implement awakeFromNib to set up the internals and layoutSubviews to position and size everything.
Here is an outline of how to do it...
1. Create a UIButton sub-class (.h)
Add the custom interface to the header file. It could look something like this.
#interface MyButton : UIButton
- (void)setServiceText:(NSString *)serviceText;
- (void)setPriceText:(NSString *)priceText;
- (void)setTimeText:(NSString *)timeText;
#end
2. Add controls to hold the internals of your button (.m)
The three labels and a view to use as the red sidebar. From here on you add to the code file.
#interface MyButton ()
#property (strong, nonatomic) UILabel *serviceLabel;
#property (strong, nonatomic) UILabel *priceLabel;
#property (strong, nonatomic) UILabel *timeLabel;
#property (strong, nonatomic) UIView *redSideBarView;
#end
3. Add code corresponding to the interface (.m)
Since UILabel redraws itself when we set the text property, we do not need to do more to make it appear on the button.
- (void)setServiceText:(NSString *)serviceText {
_serviceLabel.text = serviceText;
}
- (void)setPriceText:(NSString *)priceText {
_priceLabel.text = priceText;
}
- (void)setTimeText:(NSString *)timeText {
_timeLabel.text = timeText;
}
4. Implement awakeFromNib (.m)
This method will be called when Storyboard instantiate your button, so here is a good place to create your labels and do other stuff that only needs to be done once.
- (void)awakeFromNib {
[super awakeFromNib];
_sideBarView = [[UIView alloc] initWithFrame:CGRectZero];
_sideBarView.backgroundColor = [UIColor redColor];
[self addSubview:_sideBarView];
_serviceLabel = [[UILabel alloc] initWithFrame:CGRectZero];
_serviceLabel.font = [UIFont systemFontOfSize:18.0];
[self addSubview:_serviceLabel];
_priceLabel = [[UILabel alloc] initWithFrame:CGRectZero];
_priceLabel.textColor = [UIColor greenColor];
_priceLabel.font = [UIFont systemFontOfSize:16.0];
_priceLabel.textAlignment = NSTextAlignmentRight;
[self addSubview:_priceLabel];
_timeLabel = [[UILabel alloc] initWithFrame:CGRectZero];
_timeLabel.font = [UIFont systemFontOfSize:14.0];
[self addSubview:_timeLabel];
self.clipsToBounds = YES;
}
5. Add code to layout your button (.m)
This is the final piece of the custom button code. Note that layoutSubviews will usually be called several times during the controls lifetime, so do not add sub-views here. Use it to position and size the internals of your button. self.bounds.size represents the current size of your button, so this is a good reference for all other elements.
- (void)layoutSubviews {
// Layout sub-elements
CGFloat buttonWidth = self.bounds.size.width;
CGFloat buttonHeight = self.bounds.size.height;
_serviceLabel.frame = CGRectMake(30.0, 2.0, buttonWidth * 0.7, buttonHeight * 0.5);
_priceLabel.frame = CGRectMake(buttonWidth - 40, 5.0, 30.0, buttonHeight * 0.4);
_timeLabel.frame = CGRectMake(30.0, buttonHeight * 0.5, buttonWidth * 0.7, buttonHeight * 0.4);
_sideBarView.frame = CGRectMake(0.0, 0.0, 10.0, buttonHeight);
self.layer.cornerRadius = 10.0;
}
6. Use it!
To use it you create a regular button in Storyboard, then in the Identity Inspector, select your new button class. As usual, tie the button to a variable in your view controller class. The variable should be of your button class, of course. That's it for the design!
#property (weak, nonatomic) IBOutlet MyButton *button;
Now don't forget to set the properties.
self.button.backgroundColor = [UIColor lightGrayColor];
[self.button setServiceText:#"FULL SERVICE HAIRCUT"];
[self.button setPriceText:#"$30"];
[self.button setTimeText:#"30 minutes"];
A couple of things I didn't address here was the gradient and the drop shadow. The gradient is probably best done with a CAGradientLayer added to the button's view's layer. The drop shadow needs a bit more coding, since you are clipping the button to have rounded corners. Probably you need to add one more UIView in between to contain everything that you then clip, and then add shadow to the button view.
I wouldn't go with a UIButton but with a UIView with Labels from Xib. Tutorial on that can be found here: link.
Then you may add a target to your UIView, so that when tapping it will call some method (as would UIButton do):
yourView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(buttonTapped))
yourView.addGestureRecognizer(tap)
And method:
#objc func buttonTapped() {
// do what you need on tap here
}
Hope it helps! Please comment if you have questions.
PS. I'm not sure but from what I see you are probably building a UITableView. is it? Can you please show a design of the full screen? If you have many "buttons" like that then it's not a single "button" but a Table View.
To display multiple lines use below code
button.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
button.titleLabel.numberOfLines = 2; // or 0(Zero)
And you need to add UIView and add tap gesture to that view and design that view like above image.
For Gestures
//Create tap gustures for view
UITapGestureRecognizer *viewTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(onClickView:)];
[self.yourViewName addGestureRecognizer: viewTap];
- (void) onClickView:(UITapGestureRecognizer *)recognizer {
//Write your code
}
This is one approach.
And second one is...
For this use UITableViewCell and design that cell.
This is second approach.

Issue with presenting an external UIView in a UIViewController: outlets don't show up

I have a UIViewController that has its UI elements setup in storyboard, and things are showing up fine. Now I created a new UIView in separate xib, .h and .m files, I'll call it "overlay"; then I present that overlay which should cover everything below, so that only the overlay can be seen:
// in controller.m
OverlayView *overlayView = [[OverlayView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
overlayView.layer.zPosition = 1000;
[self.view addSubview:overlayView];
The overlay appears in a weird position (the top of it is some 50px away from the bottom of the navigation bar). What's more, apart from the background of the overlay, no elements in the overlay can be seen. It's just a blank red canvas. I have double checked that the elemenets' alpha values are 1, they are set to be not hidden, and they are also set explicitly in the initWithFrame of the overlay:
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor redColor]; // red canvas shows up
self.title.textColor = [UIColor blackColor]; // title can't be seen; why not?
self.body.textColor = [UIColor blackColor]; // body can't be seen; why not?
}
return self;
}
The reason I don't directly put overlay in the controller's xib and then simply change it's hidden property is that this overlay is to be used by multiple controllers, and I'd like to re-use it, thus putting it as a separate view.
What am I doing wrong?
The parent view (view controller's view) is responsable to set the child view's frame. If you use Auto Layout you need to use setTranslateAutoresizingMaskIntoConstraints to NO and add the desired constraints in code. Keep in mind that the child's frame will be set after viewDidLayoutSubviews is called.
If you don't use Auto Layout just set the frame using setFrame method in viewWillAppear.
It turns out I had to load the nib from within the view's own initWithFrame.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
id mainView;
if (self) {
NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:#"MyOverlayView" owner:self options:nil];
mainView = [subviewArray objectAtIndex:0];
}
return mainView;
}
- (void)awakeFromNib {
[super awakeFromNib];
}
The code above fixed it.

UIView On Top of Other Views Not Working

I do not understand why my code will not place this custom view on top of the main view. Any suggestions would be great.
Here is my code:
.h
#property (strong, nonatomic) IBOutlet UIView *saved;
.m
self.saved = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 568)];
self.saved.backgroundColor = [UIColor colorWithRed:0.0/255 green:102.0/255 blue:153.0/255 alpha:1];
[self.view bringSubviewToFront:self.saved];
You need to add as subview [self.view addSubview:self.saved]; before call bringSubviewToFront.
One note: you have declared your property as IBOutlet, this macro is to denote variables that can be referred to in Interface Builder. In case you plan to create and include the subview from the Interface Builder, it is not necessary to initialise and add as subview through code.

Xibs and Scrollview

I'm still pretty new at this, so bear with me. I thought I would be able to load a Xib in a ScrollView, as I have seen apps that seem to do this, but we're are talking two different classes. But I'll ask anyway - Is there any practical way to have a scrollView with a static Xib over the top, where buttons defined in the UI don't move while a view underneath does. I'm sure it's easily doable in cocos2d, but for what I want to do, it's a bit overkill.
--- Edit ---
At the risk of embarrassing myself, I tried both possible solutions. Adding a button grammatically adds a button that moves when I scroll. Adding the nib seems to keep the scroll screen from scrolling. Here's the code, without trying to add any buttons everything works fine.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(#"View Loaded");
[mdm setMapSetupInfoWithRows:60 columns:90 cellSize:32];
[mdm initMapDataWithOriginsUsingCenter:TRUE];
NSLog(#"MapViewContoller.mapArrayCount = %d",[[mdm mapArray]count]);
// create the MapView with the screen size create by MapDataManager
mapView = [[MapView alloc] initWithFrame:[mdm mapRect]];
// Create the UIScrollView to have the size of the window, matching the window (screen) size
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:[mdm windowRect]];
[scrollView setBounds:[mdm windowRect]];
// Tell the scrollview how big it is and set other options
[scrollView setContentSize:[mdm mapRect].size];
[scrollView setBounces:NO];
[scrollView setMinimumZoomScale:.5];
[scrollView setMaximumZoomScale:10];
[scrollView setDelegate:self];
[scrollView setBackgroundColor:[UIColor darkGrayColor]];
//add the MapView as a subview of the scrollView
[scrollView addSubview:mapView];
//add the scrollView to the current one....
[[self view] addSubview:scrollView];
[[NSBundle mainBundle] loadNibNamed:#"MapViewController" owner:self options:nil];
[self generNewMap];
}
Anything else I'm trying to do wrong? after looking at this more it does seem doable.
You should set things with a hierarchy like this.
UIViewController
UIScrollView
Static Buttons etc
Then in interface builder, or code, just add the static buttons etc to the self.view.
I do everything in code so it would look something like
-(void)viewDidLoad {
//add scrollview
appScroll = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
appScroll.pagingEnabled = YES;
[appScroll setCanCancelContentTouches:NO];
appScroll.bounds = CGRectMake(0, 0, 320, 480);
appScroll.contentSize = CGSizeMake(1600, 480);
[appScroll setScrollEnabled:YES];
appScroll.bounces = NO;
appScroll.showsHorizontalScrollIndicator = NO;
appScroll.showsVerticalScrollIndicator = NO;
appScroll.clipsToBounds = YES;
appScroll.delaysContentTouches = YES;
appScroll.center = (CGPoint){ 160, 240 };
[appScroll setBackgroundColor:[UIColor darkGrayColor]];
[self.view addSubview:appScroll];
//back
backBut = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"back.png"]];
backBut.center = (CGPoint){ 40, 430 };
backBut.transform = CGAffineTransformMakeScale(0.3,0.3);
[backBut setUserInteractionEnabled: YES];
[self.view addSubview: backBut];
}
The trick is to specify the owner when loading the XIB. For example:
Define an IBOutlet on your UIViewController
#property (nonatomic, strong) IBOutlet UIView* myScrollViewContent
Create a XIB, specify the owner as your UIViewController class
Wire the components defined in the XIB to the outlets defined in the UIViewController class
Then in code do this:
//Because the owner is 'self' the bundle loader will inject any properties defined . .
//. . . . and wired in the XIB to the owner's outlets
[[NSBundle mainBundle] loadNibNamed:#"MyXibName" owner:self options:nil];
//Now do something with self.myScrollViewContent - ie add it to the scroll view.
Custom Scroll-view sub-class
If you wanted should be able to use the same approach by creating a cusom scroll-view subclass and specifying an outlet there. . . The UIView would be loaded directly onto the sub-class, then. . . (you'd still have to add it as sub-view).
For more complex requirements, I personally like to build my views with pure code, but it is possible to arrange things neatly with XIBs.

why my actual UI layout is not same with my designed layout in xib

this is what I designed in xib, and it's my expectation also.
My ViewControlle.h file:
#interface DetailHeadViewController : UIViewController
#property (weak, nonatomic) IBOutlet UIScrollView *slideScrollView;
#property (weak, nonatomic) IBOutlet UIImageView *smallImageView;
#property (weak, nonatomic) IBOutlet UIImageView *barBGVImageView;
#end
My ViewController.m file (part of the source code)
- (void)viewDidLoad
{
[super viewDidLoad];
slideScrollView.pagingEnabled = YES;
slideScrollView.showsHorizontalScrollIndicator = NO;
slideScrollView.showsVerticalScrollIndicator = NO;
UIImageView * page1 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"headSection_hot1"]];
[slideScrollView addSubview:page1]; //Update 3
}
the result is this,
But actually the correct result is this,
Anyone can help to point out what the problem is?
Thanks in advance
update (remove bar image)
for easier, I remove bar image in xib and also comment out the relatived code.
Please see the following picture.
update 2
IB
To me it looks like your scroll view frame is not set correctly. It should probably be set to 0,0,480,320.
It also looks like the transparency isn't set on the BarImageView. Select the BarImageView in the xib, open up the inspector window, click on the 4th tab, set the background color to Clear Color and reduce the alpha.
You may also want to post how your adding the image to the scroll view as that could create issues as well.
Update 1:
In the viewDidLoad of your DetailHeadViewController try this:
self.scrollView.frame = self.view.bounds;
UIImageView * page1 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"headSection_hot1"]];
page1.frame = self.view.bounds;
[self.slideScrollView addSubview:page1];
self.scrollView.contentSize = self.view.bounds.size; // Note that the contentSize must be changed
// from this if you want to scroll.

Resources