I want to have precise control over the custom view I add to my UINavigationController toolbar. More specifically.. i want to display a UILable ontop of the items in my toolbar.
I have a toolbarItems initially set up with some UIBarButtonItems. The effect I'm trying to achieve is programmatically expand the height of the toolbar, and then display a UILabel ontop of the rest of the buttons.. this is what I currently have:
-(void)expandToolBar:(NSString *)title {
UIToolbar* toolBar =self.navigationController.toolbar;
CGRect toolbarFrame = toolBar.frame;
[UIView animateWithDuration:0.25f delay:0
options:UIViewAnimationOptionLayoutSubviews animations:^{
// expand toolbar frame vertically
[toolBar setFrame:
CGRectMake(toolbarFrame.origin.x,
toolbarFrame.origin.y-15,
toolbarFrame.size.width,
toolbarFrame.size.height + 15)];
} completion:^(BOOL finished){
[UIView animateWithDuration:0.50f animations:^{
// some code here to move the existing toolbar items lower
// ie to make space for the label
UILabel* label = [[UILabel alloc] initWithFrame:labelFrame];
[label setBackgroundColor:[UIColor clearColor]];
label.text = title;
UIBarButtonItem *labelItem = [[UIBarButtonItem alloc]
initWithCustomView:label];
// add label to toolbar
NSMutableArray *newItems = [self.toolbarItems mutableCopy];
[newItems addObject:labelItem];
self.toolbarItems = newItems;
}];
}];
}
the result of this is that all the existing buttons get squashed, and the label takes their place. The problem is that If I try to get a little too creative and start manually messing with the subviews of the toolbar, I start wandering into undocumented API land, something Apple won't tolerate. Ideas?
What's your actual question? If what you're doing is legal or not? I use some similar tricks to get from the UIBarButtonItem to the view that is represented from it, and it has never been a problem.
For example, I use following snippet without any issues. Technically this isn't using private API per so, but relying on undocumented view structure and here also part of the class names, so you really should know what you're doing. Please also file a radar that UIBarButtonItem is messed up and misses an obvious flag to get to the actual view.
static UIView *PSToolbarViewForBarButtonItem(UIToolbar *toolbar, UIBarButtonItem *barButtonItem) {
UIView *barButtonView = nil;
for (UIControl *subview in toolbar.subviews) {
if ([NSStringFromClass([subview class]) hasPrefix:#"UIToolbarB"] && [subview isKindOfClass:[UIControl class]]) {
for (UIBarButtonItem *aBarButtonItem in [subview allTargets]) {
if (barButtonItem == aBarButtonItem) { barButtonView = subview; break; }
}
}
}
return barButtonView;
}
Also, if you go that route, write code that will fail gracefully if for any reason the view for the toolbar can't be found. I know some apps that go my route, while lots of others don't even bother and simply write their own code to create a toolbar.
Related
I've added a UIView (which contains the UIImageView for the background, three UIButtons which say "Test" and the final UIButton to dismiss the view called "Finished") as a subview of my UIActionSheet.
Why won't any of these buttons detect touches? My UIView has User Interaction Enabled checked.
I'd appreciate some help with this as I've been pulling my hair out (not literally of course)!
Here's my set up:
If you're just looking mimic the UIActionSheet, I'd just create a UIVew subclass called something like "CoolActionSheet" and programatically place the buttons on there. Then, when you press the buttons it triggers a delegate method in a protocol which will be implemented in your main view controller so do something.
To show and hide the action picker use UIView animations in the CoolActionSheet class like so:
-(void)showSheet {
NSLog(#"Showing sheet...");
//Set the x/y position of the action sheet to JUST off-screen
CGFloat xPos = parentView.frame.origin.x;
CGFloat yPos = parentView.frame.size.height+kActionSheetHeight;
[self setFrame:CGRectMake(xPos, yPos, kActionSheetWidth, kActionSheetHeight)];
/*Here is where you would add your other UI objects such as buttons
and set their #selector to a method in your CoolActionSheet protocol. You could then implement this delegate method in
your main view controller to carry out a custom action. You might also want to add a background image to the view or something else.
For example: */
UIButton *coolButton = [[UIButton alloc] initWithFrame:buttonDimensions];
[coolButton addTarget:self action:#selector(didDismissActionSheet) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:coolButton];
[self.parentView addSubview:self.view];
//Slide the sheet up from the bottom of the screen
[UIView animateWithDuration:0.2 animations:^(void) {
//Slide banner in from left to right
[self setFrame:CGRectMake(0, yPos-kActionSheetHeight, kActionSheetWidth, kActionSheetHeight)];
}];
}
And to hide:
-(void)hideSheet {
NSLog(#"Hiding");
CGFloat xPos = parentView.frame.origin.x;
CGFloat yPos = parentView.frame.size.height+kActionSheetHeight;
[UIView animateWithDuration:0.2 animations:^(void) {
[self setFrame:CGRectMake(xPos, yPos, 320, 65)];
}completion:^(BOOL finished) {
[self removeFromSuperview]; //Clean up
}];
}
You may also want to grey-out the parent view. Again, in the CoolActionSheet.m:
-(void)shadeParentView {
UIView *shadedView = [[UIView alloc] initWithFrame:CGRectMake(, 0, 320, 480)];
[shadedView addGestureRecognizer:gestureRecognizer];
[shadedView setBackgroundColor:[UIColor blackColor]];
[shadedView setAlpha:0.0];
[self addSubview:shadedView];
[UIView animateWithDuration:0.5 animations:^{
[shadedView setAlpha:0.5];
}];
}
You would need to set your parentView to the view controller's view. So, to call the action sheet in your main view controller you would:
CoolActionSheet *coolSheet = [[CoolActionSheet alloc] init];
[coolSheet setParentView:self.view];
[coolSheet setSheetDelegate:self]; //set the delegate to implement button press methods in this view controller
This might seem a bit long-winded, but it's a good MVC pattern to separate it out into another view class like this. And you now have a custom class that you can just import into any other project and it'll work!
I haven't had a chance to test out this specific code, but the whole approach is good. Points to take away from this:
Use a custom UIView class
Implement delegate methods to execute tasks in your main view controller when a button is pressed in your subview.
Implement a good MVC structure to avoid spaghetti code.
Let me know if this helps :)
Try This:
[actionSheet showInView:self.view.window];
Hi all I am trying to add a UITextField to my cameraOverlay. I am getting it to show up on my camera view, but it is not editable. Its not responding at all. What approach do I need to take to get it to repsond?
I have looked at this questions, but do not understand how to set a transparent View Controller on top of my cameraOverly.
Suggestion for camera overlay
Thanks in advance!
Steps should be:
Create your picker.
Create an empty view with clearColor background.
Add your textfield with addSubview: to the view in step 2.
Set cameraOverlayView to the view created in step 2.
Present your picker.
In code:
//self.picker and self.textField being your UIImagePickerController and UITextField instances.
emptyView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)]; //This frame will make it fullscreen...
emptyView.backgroundColor = [UIColor clearColor];
[emptyView setAlpha:1.0]; //I think it is not necessary, but it wont hurt to add this line.
self.textField.frame = CGRectMake(100, 100, self.textField.frame.size.width, self.textField.frame.size.height); //Here you can specify the position in this case 100x 100y of your textField preserving the width and height.
[emptyView addSubview:self.textField];
self.picker.cameraOverlayView = emptyView; //Which by the way is not empty any more..
[emptyView release];
[self presentModalViewController:self.picker animated:YES];
[self.picker release];
I typed the code here myself so it could have some typo, Hope it helps!
I have a UITextView inside of a UIScrollView that is configured offscreen when the code below runs in my ViewController's viewDidLoad. Unfortunately, if the "eventDetails" text is particularly long nothing is displayed inside the UITextView until I interact with it (e.g click inside and drag for example).
My question: How to have it so the text is displayed in the UITextView WITHOUT forcing the user to interact with the UITextView first?
Here is the code:
UITextView *txtDetails = [[UITextView alloc] initWithFrame:CGRectMake(-4, yOffset, page3ScrollView.frame.size.width, 0)];
[txtDetails setEditable:NO];
[txtDetails setScrollEnabled:NO];
[txtDetails setFont:[UIFont systemFontOfSize:12]];
[txtDetails resizeAndSetTextWithMaxSize:CGSizeMake(txtDetails.frame.size.width, 999999999) forText:eventDetails withAdditionalHeightOf:16.f];
[page3ScrollView addSubview:txtDetails];
CGRect frame = txtDetails.frame;
frame.size.height = [txtDetails contentSize].height;
txtDetails.frame = frame;
[page3ScrollView setContentSize:CGSizeMake(page3ScrollView.frame.size.width, txtDetails.frame.origin.y + txtDetails.frame.size.height)];
Thanks
I was able to get this working by setting the UITextView's text ONLY when needed (e.g. is about to come on screen). Below is the relevant code:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sv
{
if (sv == scrollView) [self updatePagedView];
}
- (void)updatePagedView
{
int currentPage = pageControl.currentPage;
// *** loadDetailsPage3 is where I set the text ***
if (currentPage == 2) {
[self loadDetailsPage3];
}
}
If anyone has a better solution or needs more explanation just hit me up in the comments.
I've had the same issue and it's taken me several hours to find a suitable fix for it... Here's what I came up with...
-(void)DidBecomeActive {
if (!DidUpdateBounds) {
DidUpdateBounds = true; // instance variable set to false on load
NSString *oldtext = uiTextView.text;
//The trick is (1) removing it from it's superview
// (2) resetting it's 'text' property
// (3) re-adding it to the view WHILE it's on-screen.
[uiTextView removeFromSuperview];
uiTextView.text = oldtext;
[self.view addSubview:uiTextView];
[self.view setNeedsDisplay];
}
}
I'm using CoreAnimation to switch to this view. So right before I execute that code, I call this
//The 0,-300,480,300 is bounds of the UIView my offscreen UITextview is on
[self.view.layer setBounds:CGRectMake(0, -300, 480, 300)];
// SetNeedsDisplay, then call my method above to
//FORCIBILY redrew the UITextView while it's effectively on-screen
[self.view setNeedsDisplay];
[TextLogVC DidBecomeActive];
//CoreAnimation then goes here
This solves the issue. After setting the layer.bounds it redraws the control(UITextView) and it thinks it's onscreen. Then CoreAnimation takes care of animating from my current UIView to the new UIView and all of the text in the uiTextView is displayed throughout the animation.
Looks like the views that you are setting up are not refreshed.
And when you interact with the views then they are refreshed and your changes are rendered to the display...
Try adding [txtDetails setNeedsDisplay]; [page3ScrollView setNeedsDisplay]; after your code in viewDidLoad.
If it will help then maybe we'll find some more elegant solution...
How can I use UISegmentedControl to load different subviews when different segments are selected? Im new to objective-c and iOS programming.
OR is there a way to make UITabBarController look like a UISegmentedControl?
For a programatic approach
in loadView:
{
NSArray *segments = [NSArray arrayWithObjects:#"Left", #"Right", nil];
segmentedControl = [[UISegmentedControl alloc]initWithItems:segments];
[segmentedControl addTarget:self
action:#selector(changeSubViews)
forControlEvents:UIControlEventValueChanged];
contentView = [UIView alloc]initwithFrame:(the frame where you want the subViews to be displayed)];
[self.view addSubView:contentView];
}
- (void)changeSubViews
{
switch(segmentedControl.selectedSegmentIndex)
{
case 0:
{
[rightView removeFromSuperView];
if (leftView ==nil){leftView alloc, init;}
[contentView addSubView:leftView];
break;
}
case 1:
{
[leftView removeFromSuperView];
if (rightView ==nil){rightView alloc, init;}
[contentView addSubView:rightView];
break;
}
}
}
You could add a UIToolbar to your root controller's view. In it, you'd have a UISegementedControl with actions that the root controller handle. Depending on the segment clicked, you would load up a different view and display the view under the UIToolbar (and anything else that you want the view to be below).
Hope this helps!
You should consider crafterm's answer in this post: UISegmentedControl Best Practice
This will allow you to maintain your normal ViewController behavior (support rotation, memory warnings, etc.) while allowing for the segmented control on top of it.
Ok for this purpose you make two views in your view and make property for both in .h file
and
Attach an IBAction to the segmented control and write code like this
if(self.yourSegmentedControl.selectedSegmentIndex==0)
{
view1.hidden=YES;
view2.hidden=NO;
}
else if(self.categorySegmentedControl.selectedSegmentIndex==1)
{
view2.hidden=YES;
view2.hidden=NO:
}
Hope this will help you.
I am attempting to create a transition between two subviews (view1 and view2). When a button is pressed I want view1 (front) to flip and show view2 (back). I have tried both transitionFromView and transitionWithView. Each works - but each has a problem.
transitionFromView - flips the superview (the whole window view flips, not the subviews). When this flip happens - one subview is on the front of the superview before the flip, and the other subview is on the back of the flip - as it should be. But I don't want the superview to flip, just the subviews.
transitionWithView - flips only the subviews - but the 'to' view gets displayed before the transition happens.
Anyone have a suggestion?
-(IBAction) button1action:(id) sender {
if ([sender tag] == 0) {
[UIView transitionFromView:view2 toView:view1 duration:2.0
options:UIViewAnimationOptionTransitionFlipFromLeft
completion:nil];
}
else {
[view1 removeFromSuperview];
[self.view addSubview:view2];
[UIView transitionWithView:view2
duration:2.0
options:UIViewAnimationOptionTransitionFlipFromRight +
UIViewAnimationOptionShowHideTransitionViews
animations:^{}
completion:nil];
}
}
You need to remove and add the subviews in the animation block. Also, I think that transitionWithView is supposed to take the super view as argument. I think what you need to do to get this right is to use a container view that is the same size as the views you want to flip.
This is copied from the documentation:
[UIView transitionWithView:containerView
duration:0.2
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{ [fromView removeFromSuperview]; [containerView addSubview:toView]; }
completion:NULL];
I have run into this exact same problem, and I agree with the earlier answer. It seems you must have a container view for animating a transition from one subview to another subview.
I'm using the transitionFromView approach. To have a background behind the animation, you will also need a superview with the background for the container view.
My code looks like:
[UIView transitionFromView:view1
toView:view2
duration:0.5
options:UIViewAnimationOptionTransitionFlipFromRight |
UIViewAnimationOptionAllowUserInteraction |
UIViewAnimationOptionBeginFromCurrentState
completion:nil];
Here's my working solution stub, a simple thing that took 2 hours, damn:
we got 1 button to flip things, a UIView called panel that holds the 2 views I want to swap with a flip animation.
-(void)viewDidLoad{
[super viewDidLoad];
UIButton *btnFlip = [UIButton buttonWithType:UIButtonTypeRoundedRect];
btnFlip.frame = CGRectMake(10, 10, 50, 30);
[btnFlip setTitle:#"flip" forState:UIControlStateNormal];
[btnFlip addTarget:self action:#selector(flip) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btnFlip];
panel = [[UIView alloc] initWithFrame:CGRectMake(10, 40, 300,300)];
panel.backgroundColor = [UIColor darkGrayColor];
[self.view addSubview:panel];
panelBack = [[UIView alloc] initWithFrame:CGRectMake(10, 40, 280, 200)];
panelBack.tag = 1;
panelBack.backgroundColor = [UIColor brownColor];
[panel addSubview:panelBack];
panelFront = [[UIView alloc] initWithFrame:CGRectMake(10, 40, 280, 200)];
panelFront.tag = 2;
panelFront.backgroundColor = [UIColor whiteColor];
[panel addSubview:panelFront];
displayingFront = TRUE;
}
-(void)flip{
[UIView transitionWithView:panel
duration:0.5
options:(displayingFront ? UIViewAnimationOptionTransitionFlipFromRight : UIViewAnimationOptionTransitionFlipFromLeft)
animations:^{
if (displayingFront) {
//panelFront.hidden=TRUE;
//panelBack.hidden = FALSE;
[panelFront removeFromSuperview];
[panel addSubview:panelBack];
}else{
//panelFront.hidden=FALSE;
//panelBack.hidden = TRUE;
[panelBack removeFromSuperview];
[panel addSubview:panelFront];
}
}
completion:^(BOOL finished){
displayingFront = !displayingFront;
}];
}
Having run into the same problem I agree with Eric and Sam:
either transitionWithView or transitionFromView will both do what you want as given above as long as you create a container view of the appropriate size that you wish to flip. Otherwise the whole window view will flip.
So if view1 is the subview you begin with and you want to flip to view2 then add
UIView *containerView = [[UIView alloc] initWithFrame:appropriateSize];
[containerView addSubview:view1];
And then the simplest way to animate is probably:
[UIView transitionFromView:view1
toView:view2
duration:2.0
options:UIViewAnimationOptionTransitionFlipFromLeft
completion:nil];
I had the same requirement, and saw many approaches -- from using layers (which ends up very complicated if you just want to deal with a UIView) to suggestions that i needed a "container" to then transition between to sub-views. But if you just want to swithc something front to back, like flipping over a playing card or a game tile, i made a one line change:
(note: i have an array of UIViews (tiles[x][y]) in a grid (for a game). i have all my imagefilenames in database tables / arrays, which dynamically are loaded into the UIImages of the UIImageViews. The image for the "back" of a card or tile in a UIImage named "tileBackPicImageNM".
so my code that simply swapped the front image out for an image of the back was:
[tiles[indexX][indexY] setImage:[UIImage imageNamed:tileBackPicImageNM]];
which works fine.
but and now, to show a flipping action, it is:
[UIView transitionWithView:tiles[indexX][indexY] duration:0.3 options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{
[[indexX][indexY] setImage:[UIImage imageNamed:tileBackPicImageNM]];
}
completion:NULL];
I experimented with different "duration" parameters -- subsequently made it a float that I set in the viewDidLoad routine. Faster than 0.3 is almost not visible, and a value of 1 or 2 is very slow...
This does not involve two sub-views, which is my case is a lot more useful since i didn't want to have an array of containers containing a hierarchy of views, etc... and on new levels etc i always reload the arrays anyway...
Keep in mind that the container view requires a solid background color.It means anything else than clear color.This was my mistake and spent quite long time to figure it out.
using transitionFromView, both front and back view should be inside another view(not the super view). this way it will not flip the whole screen.
> superview
> another view
> backview
> frontview
UIView.transitionFromView(frontView, toView: backView, duration: 0.5, options: .TransitionFlipFromLeft | .ShowHideTransitionViews) { finished in
}
You might want to make the (another view) equal to the size of the frontView