I have an app with a UITableView of results in a center column, and a little search bar at the top. I want to dynamically add/remove a button that says "reset search" and pin it to the top of the view.
There are a couple ways to go about it, and I'm worried that they both look ugly or hacky to me. To wit:
Add the button in the storyboard editor, and show/hide it in code. The trouble is I've already got a bunch of views specified this way in the storyboard, and so positioning/selecting them is a huge pain since they overlap each other.
Add the button in code. Except now my UI is specified in two places: the stuff that's in the storyboard, and the additional modifications that take place in the code.
What's the standard way of doing something like this? And how can I prevent my storyboards from becoming a big mess when I've got buttons/dialogs/etc. that need to be dynamically shown/hidden?
Well my first answer is to not use storyboards in the first place. However, I understand that's not helpful in this case.
If I were you, I would do option 2. It's a one off for this single button and it has a specific use case. It doesn't hurt to specify it in code. The following is for the
.h
#property (nonatomic, strong) UIButton *resetButton;
And
.m
//I'm guessing you're using a VC, so I'd put this in viewDidLoad
self.resetButton = [[UIButton alloc]initWithFrame:YOUR FRAME];
self.resetButton.alpha = 0.0;
//any other styling
[self.view addSubview:self.resetButton];
self.resetButton addTarget:self action:#selector(onReset) forControlEvents:UIControlEventTouchUpInside];
//and then add these three methods
- (void)onReset {
//called when reset button is tapped
}
- (void)showResetButton {
[UIView animateWithDuration:.3 animations:^{
self.resetButton.alpha = 1.0;
}];
}
- (void)hideResetButton {
[UIView animateWithDuration:.3 animations:^{
self.resetButton.alpha = 0.0;
}];
}
I don't know if I have understood, but if you want to hide an object with an action, you can do so :
- (IBAction)myaction:(id)sender
{
self.object1.hidden = false ;
self.object2.hidden = true ;
self.object3.hidden = false ;
}
Both ways are perfect, I personally prefer the Storyboard one because it lets you arrange the button more easily and it's easier to add constraints for auto-layout(if needed) in Interface Builder than in code.
For your second question: If your storyboard is cluttered and views are all over the place, I would suggest that you select your views from the side bar, rather thank trying to click on them. Also, if you want to move the selected view, adjust the coordinates in the Utilities panel instead of dragging it with the mouse.
Related
I am using https://github.com/CEWendel/SWTableViewCell library to my project.
Certain situation I need to disable the particular button action of swipe cell.
I cannot find any property in their class file. If anyone crossed this, give me answer.
Here I have attaced my swipe options image:
For ex
: I want to disable the share button action.
Let's assume your share button is in the leftButtonsArray. In the method:
- (void)swipeableTableViewCell:(SWTableViewCell *)cell scrollingToState:(SWCellState)state
{
//case:left buttons opened
UIButton *shareButton = leftButtonsArray[theIndexOfTheShareButton];
shareButton.enabled = NO;
}
#karthikeyan You can hide the button for a particular row in tableview by the following code:
- (void)updateRightUtilityButtons:(NSArray *)rightUtilityButtons WithButtonWidth:(CGFloat) width {
_rightUtilityButtons = rightUtilityButtons;
[self.rightUtilityButtonsView updateUtilityButtons:rightUtilityButtons WithButtonWidth:width];
[self.rightUtilityButtonsView layoutIfNeeded];
[self layoutIfNeeded];
}
Add/update this methods to SWTableViewCell.m class, where rightUtilityButtons is an array of buttons you need to display for the particular row.
In case if you want to disable just user interaction you can achieve while adding button into array, just disable user interaction for that button by shareButton.userInteration = NO and then add to array and then pass the array to the method defined above. By this you can be sure that button is disabled.
But please provide the sample code that you have worked so that can update your code directly.
In case if you still didn't get revert back I'll give you the working code directly here.
I have an application using PKRevealController which implements a slide-out menu similar to the ones in the popular Facebook and GMAIL apps on iOS. The app is built in XCode 5, and runs on iOS 6 and iOS 7. I need to figure out how to have it work sanely in both places, so a simple .XIB hack that makes it look okay in iOS 7 but makes it look worse in iOS 6 is not okay.
The code works great for iOS 6, where the status bar is opaque and the top view is not alpha-blended with the status bar.
However, on iOS 7, just for example, I have created this view in my .xib file, here is how it appears running in ioS 6 simulator, shown here with the slide out menu opened:
The same .xib file running on ios 7, when the slide-out menu open, the top of the slide out menu's .xib content is now under the status bar, as Apple said it would be in their ios 7 transition guide:
The class I need to modify in PKRevealController is probably the presenting view controller that is creating and presenting the contained view, the contained view is called PKRevealControllerContainerView, I think. I think I probably need to create
some kind of view hierarchy like this:
[ Outermost View container
[ some kind of blob to occupy the header area ]
[ the client view I want to appear the way it did in iOS 6]
]
I've been reading around, and there may be much simpler approaches, but I don't quite understand them, approaches like adding properties to my info.plist, like View controller-based status bar appearance = YES. I tried that it did not have the desired effect.
How do I go about fixing this? I have read the Fine Guide published by Apple and it has not provided code, only general guidance like this page on the status bar.
It's easy to replicate this problem, just clone the git repo https://github.com/pkluz/PKRevealController, build and run.
The code that brings up the pop-up view looks like this:
- (void)addLeftViewControllerToHierarchy
{
if (self.leftViewController != nil && ![self.childViewControllers containsObject:self.leftViewController])
{
[self addChildViewController:self.leftViewController];
self.leftViewContainer.viewController = self.leftViewController;
if (self.leftViewContainer == nil)
{
self.leftViewContainer = [[PKRevealControllerContainerView alloc] initForController:self.leftViewController shadow:NO];
self.leftViewContainer.autoresizingMask = [self autoresizingMaskForLeftViewContainer];
}
self.leftViewContainer.frame = [self leftViewFrame];
[self.view insertSubview:self.leftViewContainer belowSubview:self.frontViewContainer];
[self.leftViewController didMoveToParentViewController:self];
}
}
The above is invoked by PKRevealController.m, like this:
- (void)showLeftViewControllerAnimated:(BOOL)animated
completion:(PKDefaultCompletionHandler)completion
{
__weak PKRevealController *weakSelf = self;
void (^showLeftViewBlock)(BOOL finished) = ^(BOOL finished)
{
[weakSelf removeRightViewControllerFromHierarchy];
[weakSelf addLeftViewControllerToHierarchy]; // HELLO LEFT Slide-out menu.
....
Is there a better approach than my idea? Did Apple provide some way to make this easy or does trying to support iOS 6 and iOS 7 in a single codebase leave me doing hacks like the above I'm considering?
Here, for instance, is a really ugly hack where I don't bother placing any view underneath the apple system status bar, leaving a black bar at the top, which is no good, but it shows I'm modifying the right area in the code, at least:
- (void)addLeftViewControllerToHierarchy
{
CGRect lvFrame;
if (self.leftViewController != nil && ![self.childViewControllers containsObject:self.leftViewController])
{
[self addChildViewController:self.leftViewController];
self.leftViewContainer.viewController = self.leftViewController;
if (self.leftViewContainer == nil)
{
self.leftViewContainer = [[PKRevealControllerContainerView alloc] initForController:self.leftViewController shadow:NO];
self.leftViewContainer.autoresizingMask = [self autoresizingMaskForLeftViewContainer];
}
lvFrame = [self leftViewFrame];
lvFrame.origin.y += 20; // ugly hack demo code only! don't really do it this badly!
lvFrame.size.height -= 20;
self.leftViewContainer.frame = lvFrame;
[self.view insertSubview:self.leftViewContainer belowSubview:self.frontViewContainer];
[self.leftViewController didMoveToParentViewController:self];
}
}
The above hack is almost enough, if I also add this to UIViewController+PKRevealController.m:
-(UIStatusBarStyle)preferredStatusBarStyle{
return UIStatusBarStyleBlackOpaque;
}
The above code, when added, causes the following hint/warning:
Category is implementing a method that will also be implemented by its primary class.
I'm including the above notes to show what I've tried, and I welcome some idea of how the real experts are doing this.
My own modified copy of the PKRevealController code, including the hack above, in a slightly improved form, is found here: https://github.com/wpostma/PKRevealController
I've been struggling with PKRevealController as well. While I'm still looking for better solutions I will share what I came up with until now.
My two problems were:
Status bar style was always the same and I wanted a different style for the front view and the menu;
The menu view top cell (it's a table view controller) showed up behind the status bar.
1. Dynamic status bar style
First I had my own PKRevealController subclass where I was having a custom initialiser and some custom methods to load new view controllers into the front view navigation view controller. But that's not relevant for now.
I used this subclass to implement preferredStatusBarStyle as follows so that the reveal controller can provide the right style for each state:
- (UIStatusBarStyle)preferredStatusBarStyle {
switch (self.state) {
case PKRevealControllerFocusesLeftViewController:
return [self.leftViewController preferredStatusBarStyle];
break;
case PKRevealControllerFocusesRightViewController:
return [self.rightViewController preferredStatusBarStyle];
break;
case PKRevealControllerFocusesFrontViewController:
return [self.frontViewController preferredStatusBarStyle];
break;
case PKRevealControllerFocusesLeftViewControllerInPresentationMode:
return [self.leftViewController preferredStatusBarStyle];
break;
case PKRevealControllerFocusesRightViewControllerInPresentationMode:
return [self.rightViewController preferredStatusBarStyle];
break;
default:
return UIStatusBarStyleDefault;
break;
}
}
This alone doesn't work however. You still have to say that the status bar style needs to change with setNeedsStatusBarAppearanceUpdate. As Apple says this should be called from inside an animation loop and you can find one in PKRevealController's setFrontViewFrameLinearly method. This is how it looks after I've modified it:
- (void)setFrontViewFrameLinearly:(CGRect)frame
animated:(BOOL)animated
duration:(CGFloat)duration
options:(UIViewAnimationOptions)options
completion:(PKDefaultCompletionHandler)completion
{
[UIView animateWithDuration:duration delay:0.0f options:options animations:^
{
self.frontViewContainer.frame = frame;
if ([[[UIDevice currentDevice] systemVersion] compare:#"7.0" options:NSNumericSearch] != NSOrderedAscending) {
[self setNeedsStatusBarAppearanceUpdate];
}
}
completion:^(BOOL finished)
{
safelyExecuteCompletionBlockOnMainThread(completion, finished);
}];
}
If you try it out at this point the styles will be mixed up. You can quickly conclude that by the time preferredStatusBarStyle is called the reveal controller state is still not changed. For that go to every method that sets the state, e.g. enterPresentationModeForRightViewControllerAnimated and set the state before it calls any change to the frame (the one is going to trigger the animation loop). I did it in 5 different places.
2. Left/Right menu with inset
For this one I have to say I used a workaround: I've just set a header view on the table view (tableHeaderView property).
Put this in viewDidLoad of your UITableViewController:
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0.f, 0.f, self.tableView.frame.size.width, 20.f)];
headerView.backgroundColor = [UIColor whiteColor];
self.tableView.tableHeaderView = headerView;
Don't forget to add some condition so it doesn't get executed in iOS 6. Use this other answer to know how to do it.
If you don't need iOS 5- support, you could use autolayout and align topmost views to topLayoutGuide.
So, for example, if your left view controller is a UIViewController with a UITableView in it, you could snap UITableView's top edge to the topLayoutGuide.
You can do it in (1) IB (storyboard) or (2) from code.
I personally prefer the first approach, as far as it removes the need of unnecessary code. You just open your storyboard and snap your table view's top edge to topLayoutGuide. In iOS 7 you'll end up with topLayoutGuide constraint, in iOS6 topLayoutGuide constraint is converted to a common to-container-view constant.
If you use second approach, you'll have to make sure you don't use topLayoutGuide in iOS6, something like this:
// assume you'r in your UIViewController subclass
if (![self respondsToSelector:#selector(topLayoutGuide)])
{
// topLayoutGuide is not supported, probably iOS6
// add constraints to snap tableview's top edge to superview's top edge
}
else
{
// cool, topLayoutGuide is supported, probably iOS7
// add constraints to snap tableview's top edge to topLayoutGuide
}
I had this question when/where to create and initialize views that are created programatically, so I hope some discussions here will shed more light on this topic for me.
This slide:
says: "not to initialize something based on the geometry of the view in viewDidLoad" and suggests viewDidAppear.
Imagine my view controller has view. I want to add 10 dynamic UIButtons to it.
Shall I put the code like below to the viewDidAppear?
-(void) viewDidAppear:(BOOL)animated
{
...
UIButton *button1 = [[UIButton alloc] initWithFrame: rect1];
[self.view addSubview: button1];
UIButton *button2 = [[UIButton alloc] initWithFrame: rect2];
[self.view addSubview: button2];
...
}
But this creates the buttons each time the view is shown. Is it what we want?
On the other hand if I put the code in viewDidLoad slide suggest not to initialize geometry of these views there.
Or shall we create buttons in viewDidLoad and set their frames in viewDidAppear?
What approach do you usually take?
But this creates the buttons each time the view is shown. It's true.
So the best thing you can do is to add a boolean (lets name it isLaunched). You set it to FALSE in the method -(void)viewDidLoad
Then add a if condition in your -(void)viewDidAppear where you perform creation of buttons (or other stuff) and set the boolean to true at the end.
You should have something like that :
-(void)viewDidLoad
{
//some settings
isLaunched = FALSE;
}
-(void)viewDidAppear:(BOOL)animated
{
if(!isLaunched)
{
//creating and adding buttons
isLaunched = TRUE;
}
}
zbMax (and now Amar) offered good solutions to implement the view creations in viewDidAppear: I will provide the rational for doing this (over viewDidLoad).
It is pretty simple actually. In viewDidLoad none of the views are actually setup yet, so any attempt to set/create frames or bounds will be extremely inconsistent. Struts and springs (or autolayout) will take effect after this method which will create additional changes to your views. viewDidAppear: is the correct place to do this because you can now rely on existing views and setting frames.
Reason for not playing with the geometry in viewDidLoad is because view is still in the memory and not on the window. Once the view is put on the window, then you can specify geometry. That happens when viewDidAppear is called for your controller.
As recommended, you should do all the initialisation in viewDidLoad as this is one time task and need not be repeated. Hold references to the added subviews and give them appropriate frame in viewDidAppear.
When you are dealing with custom UIView and its subviews, layoutSubviews is the method you need to override in the custom view in order to rearrange the geometry of its subviews.
Hope that helps!
I am attempting to create a menu system similar to the target app for iPhone. Here is what the menu looks like:
(source: iclarified.com)
I have been searching the internet and have not found a lot of information. To the best of my knowledge, I will have to create a UIView that is the menu, offset the view and detect when it is touched to slide up. These buttons in the menu view will have to load other views just like a tab bar controller would while keeping the menu tab to slide up at the bottom. Slide menu up, tap button for where you want to go, load that view while sliding the button back down.
The animations seem straight forward, its making it function like the tab bar controller and always stay in view and also how to detect the sliding of the menu (maybe something with UIScrollView)?
Any help would be great, no idea where to start.
Steps up to 5 contain the creation of the subview itself, but if you are only interested on the dragging events read from step five, which is about the panGestureRecognizer.
Create a custom UIView, I have also created a delegate protocol to inform the viewController when an action occurs on the subview. Depending on how much complicated your subview will be, you may want to create a xib file with the same name for that. Let's call it filterView from now on.
Create two methods like "hide" and "show" on the filterView. If you do not want to have a button (namely the small button with the arrow, I ll call it dropdownButton) to close and open the filterView, you may skip this step, but I strongly recommend to implement them. What you have to do is to animate filterView.frame.origin.y to
[[UIScreen mainScreen] bounds].size.height - dropDownButton.frame.height to hide the filterView
[[UIScreen mainScreen] bounds].size.height - filterView.frame.size.height to show it.
If requested I can also send code for that animations.
3.
Open the xib file for the UIViewController and add a UIView, so that only its dropdown button will be visible. Click the view and change its class to filterView using the rightmost pane in the interface builder. At this step you should be able to see your filtersView's tip on the bottom of the page if you did everything correctly. If not, the filterView.xib file is probably not correctly connected to filterView source code files.
4
.
Import the FilterView in the ViewController and connect as IBOutlet & synthetize it. Implement the delegate protocol if you have written one. If you have buttons on the filterView, you will need it later on. The delegate protocol should include optional messages like
-(void) featuresButtonTappedOnFilterView: (FilterView *) filterView;
and in the implementation you should do what you have to do in the viewController, like opening another viewController.
5
. On the viewControllers xib file create a panGestureRecognizer and add it to your filterView. PanGestureRecognizer is a gestureRecognizer which will handle the dragging events. At first the whole filterView will be able to dragged around by clicking any point on it, we will get to that later. Connect the gestureRecognizer as IBOutlet and create an IBAction (preferably with a better name) like:
-(IBAction)_panRecogPanned:(id)sender;
In the viewDidLoad method of the ViewController dont forget to set the delegate as:
[_panRecog setDelegate:self];
6
. Implement other states for more power. but stateChanged will be sufficient at first.
- (IBAction)_panRecogPanned:(id)sender {
switch ([(UIPanGestureRecognizer*)sender state]) {
case UIGestureRecognizerStateBegan: { } break;
case UIGestureRecognizerStateChanged: {
if ( [((UIPanGestureRecognizer *)sender) locationInView:_filterView].y > dropDownButton.frame.height )
return; // Only drag if the user's finger is on the button
CGPoint translation = [_panRecog translationInView:filterView];
//Note that we are omitting translation.x, otherwise the filterView will be able to move horizontally as well. Also note that that MIN and MAX were written for a subview which slides down from the top, they wont work on your subview.
CGRect newFrame = _panRecog.view.frame;
//newFrame.origin.y = MIN (_panRecog.view.frame.origin.y + translation.y, FILTER_OPEN_ORIGIN_Y);
//newFrame.origin.y = MAX (newFrame.origin.y, FILTER_INITIAL_ORIGIN_Y);
newFrame.origin.y = _panRecog.view.frame.origin.y + translation.y;
_panRecog.view.frame = newFrame;
[_panRecog setTranslation:CGPointMake(0, 0) inView:self.view];
} break;
//Remember the optional step number 2? We will use hide/ show methods now:
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled: {
//CGPoint velocity = [_panRecog velocityInView:_panRecog.view];
//Bonus points for using velocity when deciding what to do when if the user lifts his finger
BOOL open;
/*
if (velocity.y < -600.0) {
open = NO;
}
else if (velocity.y >= 600.0) {
open = YES;
} else
*/
if ( _panRecog.view.frame.origin.y > (FILTER_OPEN_ORIGIN_Y + FILTER_INITIAL_ORIGIN_Y) / 2 ) {
open = YES;
}
else {
open = NO;
}
if (open == YES) {
[_filterView show];
}
else {
[_filterView hide];
}
} break;
default:
break;
}
}
Note: My filter view was on the top of the page instead of the bottom, as it is the subview will move out of the page borders. Comment out the MAX and MIN statements to fix that, I am to lazy to write them by myself. The code is modified after copy&paste, it may (and probably will) contain typos.
I think you're trying to make it too complicated - just make a view and position in such a way that it is mostly off the screen, with just a tip showing up. When you touch the tip, move the view to be fully visible, and put that movement within an animation block.
The menu items could be just UIButtons with custom graphics on that view.
Don't think complex, logic is very simple. set its height and width in such a way that it is off the screen. try this approaches and then give your feedback. check http://blogsbits.com this method will help. hit and try method can be use to check these variables.
I have a UITextview, becomes first responder and keyboard is presented. Presently, I have buttons in inputAccessoryView toolbar that exchange the text forward and reverse through an array of strings.
I have the process working with no issues, so of course I am inclined to break it. My wish is to slide the textview left and right like a carousel to make it more clear to the user that next or previous string is coming and going. The current system simply replaces the text with no animation.
My first thought was to create a UINavigationController, give it an array of UIViewControllers that present the UITextviews. The navigation controllers view is only as big as the textview and I add it as a subview to my full view (which is itself in a navigation controller). I got this working fairly completely, the navigation bar is hidden and it looks no different than the original textview except that the textview now slides off to the right or left, depending if I am pushing or popping.
The problem with that is that the keyboard slides off along with the dismissed view controller, then the new textview in the new controller becomes first responder and the keyboard returns. Close, but no cigar.
I considered using page view controller but it seems it will have the same issue. I think I may have to go back to the single textview and animate the whole process directly with static screen grabs. That is completely beyond my experience level and I am think there must be a simpler way.
Can anyone suggest a simple way to keep that keyboard present while the views are swapped as described? Suggestions on other angles of attacking this?
Seems like an awful lot of overhead for a simple animation.
Try something like this (assuming ARC):
typedef enum _eDirection
{
rightToLeft = -1,
leftToRight = 1
} eDirection;
- (void) animateTextFields:(eDirection)direction
{
CGFloat distance = self.window.bounds.size.width;
UITextField *oldTextField = thisView.textField;
CGRect oldTFRect = oldTextField.frame;
CGRect newTFRect = CGRectOffset(oldTFRect, -direction * distance, 0);
UITextField *newTextField = [[UITextField alloc] initWithFrame:newTFRect];
[newTextField setText:#"whatever"];
[self addSubview:newTextField];
[UIView animateWithDuration:0.3f
animations:^{
[oldTextField setFrame:CGRectOffset(oldTextField.frame, direction * distance, 0)];
[newTextField setFrame:CGRectOffset(newTextField.frame, direction * distance, 0)];
} completion:^(BOOL finished) {
[self setTextField:newTextField];
[self removeSubview:oldTextField];
[newTextField becomeFirstResponder];
}];
}
(Disclaimer: as with all code typed off the top of one's head, etc., etc.)
This method would create a new textField, animate it horizontally onto the screen while moving the existing one off the screen in the direction you give, and then assigns the new textField as the current one and makes it the firstResponder.