I have a UIToolbar that I set up using IB with three buttons, left, middle and right. In some situations I would like to not display the middle button. Does anybody know of a way to hide a specific button on inside a UIToolBar? There is no hide property, all I can find is setEnable but this still leaves the button causing users to wonder what its purpose is. I would like to only display it in situations that it actually has a use.
Thanks in advance!
Reset the items:
-(void)setItems:(NSArray *)items animated:(BOOL)animated
You can get the current items using the items property, then just remove the one you don't want to show and pass in the new NSArray.
As you can see, you can also animate it to make it clear to the user.
Rather than guessing at the index, I added an IBOutlet for the UIBarButtonItem and then removed it by name:
NSMutableArray *toolBarButtons = [self._toolbar.items mutableCopy];
[toolBarButtons removeObject:self._selectButton]; // right button
[self._toolbar setItems:toolBarButtons];
And of course it helps to connect the outlets in the designer :)
This is how i did it.. too much headache but its the best i could come up with :
NSArray *toolBarArray = toolBar.items;
NSMutableArray *newToolBarArray = [NSMutableArray arrayWithArray:toolBarArray];
[newToolBarArray removeObjectAtIndex:2];
[newToolBarArray removeObjectAtIndex:1];
//remove whatever buttons you want to.
NSArray *finalTabBarArray =[[NSArray alloc] initWithObjects:newToolBarArray, nil];
[toolBar setItems:[finalTabBarArray objectAtIndex:0] animated:NO];
I know it is quite old thread for but those who look this page for solution, here you go :
With iOS7, you can use this approach to show/hide your toolbar button :
if(// your code Condition)
{ self.toolbarBtn1.enabled = YES;
self.toolbarBtn1.tintColor = nil; }
else
{ self.toolbarBtn1.enabled = NO;
self.toolbarBtn1.tintColor = [UIColor clearColor]; }
This does not work here because the array you are sending with setItem is not what the function expects.
I had to replace the line:
NSArray *finalTabBarArray = [[NSArray alloc] initWithObjects:newToolBarArray, nil];
with this one:
NSArray *finalTabBarArray = [newToolBarArray copy];
Then it works perfectly.
Mohit's answer is one that I have used, but you dont need to specifically make it a NSArray that the toolbar sets. You can just set the array of items as a NSMutableArray. No real advantage that I am aware off but its a few lines less code. And that way you can take the array and move about UIButton objects as you would any other array with objects and then just reset the toolbar with that mutable array.
[newToolBarArray removeObjectAtIndex:2];
[newToolBarArray removeObjectAtIndex:1];
[toolBar setItems:newToolBarArray];
Related
I am trying to create a NSCollectionView programmatically using a NSCollectionViewDataSource.
The code is very simple:
self.collectionView = [[NSCollectionView alloc] init];
// Add collection view to self.view etc.
self.collectionView.dataSource = self;
[self.collectionView registerClass:[NSCollectionViewItem class] forItemWithIdentifier:#"test"]
self.collectionView.collectionViewLayout = gridLayout;
[self.collectionView reloadData]
This leads to the following methods getting called (if I don't set the collectionViewLayout property explicitly these two don't get called either):
- (NSInteger)numberOfSectionsInCollectionView:(NSCollectionView*)collectionView
- (NSInteger)collectionView:(NSCollectionView*)collectionView numberOfItemsInSection:(NSInteger)section
However, collectionView:itemForRepresentedObjectAtIndexPath: is never called. Is there something else that I need to do in order to make sure that the last data source method is called? I have made sure that the two count calls return > 0, so that's not the problem.
So it seems that the problem was actually that I wasn't wrapping the NSCollectionView in a NSScrollView. This probably has to do with the layout being done incorrectly (so the items aren't requested from the data source) if it is not wrapped in a scroll view.
I've been working through different scenario's in the past days, and I dare say that using an NSScrollView, or not, makes practically no difference. With or without scrollView, I've ended up with the same errors and limitations.
What does make a huge difference is the choice between "old school" and the new-fangled collectionView. By "old school" I mean setting the itemPrototype and contents properties, something like this:
NSCollectionView *collectionView = [[NSCollectionView alloc] init];
collectionView.itemPrototype = [TBCollectionViewItem new];
collectionView.content = self.collectionItems;
NSInteger index = 0;
for (NSString *title in _collectionItems) {
NSIndexPath *path = [NSIndexPath indexPathForItem:index inSection:0];
TBCollectionViewItem *item = [collectionView makeItemWithIdentifier:#"Test" forIndexPath:path];
item.representedObject = title;
index++;
}
// Plays well with constraints
New school, something along these lines:
NSCollectionView *collectionView = [[NSCollectionView alloc] init];
collectionView.identifier = TBCollectionViewIdentifier;
[collectionView registerClass:[TBCollectionViewItem class] forItemWithIdentifier:TBCollectionViewItemIdentifier]; //register before makeItemWithIdentifier:forIndexPath: is called.
TBCollectionViewGridLayout *gridLayout = [TBCollectionViewGridLayout collectionViewGridLayout:NSMakeSize(250, 100)]; //getting the contentSize from the scrollView does not help
collectionView.collectionViewLayout = gridLayout;
collectionView.dataSource = self;
Now, you may have noticed the comment that registerClass: must be called before makeItemWithIdentifier:forIndexPath. In practice, that means calling registerClass: before setting .dataSource, whereas in your code you set .dataSource first. The docs state:
Although you can register new items at any time, you must not call the makeItemWithIdentifier:forIndexPath: method until after you register the corresponding item.
I wish I could say that by switching those two lines, all layout problems will be solved. Unfortunately, I've found that the .collectionViewLayout / .dataSource combination is a recipe for (auto)layout disaster. Whether that can be fixed by switching from NSCollectionViewGridLayout to flowLayout, I'm not yet certain.
I have an NSTextView that uses the find bar ([textView setUsesFindBar:YES];).
I have 2 questions.
How do I clear the visual feedback from a find operation?
My problem happens when I programmatically change the content of the textView. The visual feedback for a search operation on the previous content remains after the content change. Obviously these yellow boxes do not apply to the new content so I need a way to clear them when changing the textView content.
Note: I did not implement the NSTextFinderClient protocol because I have a simple textView and the find bar just works without any other effort.
How can I send a search string to the find bar?
I found my answers, so for others here's how to do it.
First you need an instance of NSTextFinder so you can control it. We set that up in code.
textFinder = [[NSTextFinder alloc] init];
[textFinder setClient:textView];
[textFinder setFindBarContainer:[textView enclosingScrollView]];
[textView setUsesFindBar:YES];
[textView setIncrementalSearchingEnabled:YES];
First answer: To clear visual feedback I can do either of 2 things. I can just cancel the visual feedback...
[textFinder cancelFindIndicator];
Or I can alert NSTextFinder that I'm about to change my textView content...
[textFinder noteClientStringWillChange];
Second answer: There's a global NSFindPboard. You can use that to set a search.
// change the NSFindPboard NSPasteboardTypeString
NSPasteboard* pBoard = [NSPasteboard pasteboardWithName:NSFindPboard];
[pBoard declareTypes:[NSArray arrayWithObjects:NSPasteboardTypeString, NSPasteboardTypeTextFinderOptions, nil] owner:nil];
[pBoard setString:#"new search" forType:NSStringPboardType];
NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSTextFinderCaseInsensitiveKey, [NSNumber numberWithInteger:NSTextFinderMatchingTypeContains], NSTextFinderMatchingTypeKey, nil];
[pBoard setPropertyList:options forType:NSPasteboardTypeTextFinderOptions];
// put the new search string in the find bar
[textFinder cancelFindIndicator];
[textFinder performAction:NSTextFinderActionSetSearchString];
[textFinder performAction:NSTextFinderActionShowFindInterface]; // make sure the find bar is showing
There's a problem though. The actual text field in the find bar does not get updated after that code. I found that if I toggle the first responder then I can get it to update...
[myWindow makeFirstResponder:outlineView];
[myWindow makeFirstResponder:textView];
I have a navigation bar to which I have added right BarButton successfully. Both the NavigationBar and BarButton are created programmatically. Now according to my requirement I got to add two right BarButtons to my navigation Bar. Can anyone tell me how to do this? My app is targeting ios4.
This code will do the trick for you,
NSArray *barButtonItems= [[NSArray alloc] initWithObjects:self.addButton,self.sortbyButton,nil];
self.navigationItem.rightBarButtonItems=barButtonItems;
where addButton and sortbyButton are 2 separate BarButton Items
I know it is too late but I faced it recently. So here is what I did
Create a UIView in code and add the buttons as subviews into this view.
Create a ToolbarButton using [[UIBarButtonItem alloc] initWithCustomView:buttons]
Assign this toolbar button as the Left or right barbuttonItem as U wish.
If you application is targeting iOS 4 and above then you should take UISegmentControl and have two segments on it. Catch value changed action event and check which segment is selected and do you operation accordingly.
You can also set images to segments to make look and feel better.
As the documentation to UINavigationItem1 describes, it has a property rightBarButtonItems (as well as leftBarButtonItems) where you can give an array of UIBarButtons. They are display from right (index 0) to left (index n-1).
#Matthias, As stated in documentation, rightBarButtonItems property is available from iOS 5 and above and this function needs to be supported also on iOS 4.
So, UISegmentControl is best way to achieve this.
NSArray *segmentTextContent = [NSArray arrayWithObjects:
NSLocalizedString(#\"Group By\", #\"\"),
NSLocalizedString(#\"Filter By\", #\"\"),
nil];
UISegmentedcontrol *segmentedControl = [[UISegmentedControl alloc] initWithItems:segmentTextContent];
segmentedControl.selectedSegmentIndex = 0;
segmentedControl.autoresizingMask = UIViewAutoresizingFlexibleWidth;
segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
segmentedControl.frame = CGRectMake(0, 0, 125, 30);
[segmentedControl addTarget:self action:#selector(toggleUnit) forControlEvents:UIControlEventValueChanged];
segmentedControl.tintColor = [UIColor lightGrayColor];
defaultTintColor = [segmentedControl.tintColor retain];
self.navigationItem.rightBarButtonItem = segmentedControl;
[segmentedControl release];
Let's imagine that I have 2 UIScrollViews with different UIImageViews inside. When I trigger an action I would like the contents, parameters etc (besides the location) of the 2nd UIScrollView to be passed onto the 1st UIScrollView.
So, here's the code that I've come up with:
-(void) someAction {
UIScrollView * scroll = [[UIScrollView alloc] init]; // create an intermediary scrollView
scroll = secondScroll; // here I intent to pass the content of secondScroll to scroll
scroll.frame = firstScroll.frame; // here I assign the frame of firstScroll to scroll
so that it doesn't take the frame of secondScroll;
firstScroll =scroll; // and then pass all the contents of scroll to firstScroll;
}
But when I execute that and trigger the action, the firstScroll seems to take the content of secondScroll, but secondScroll seems to be deleted. I need it to stay as is.
Any help?
Thanks
-(void) someAction {
UIScrollView * scroll = [[UIScrollView alloc] init]; // create an intermediary scrollView
That's WRONG. You'll be leaking memory with it, since you're losing the pointer to that scroll view after assigning it to a different object. You'd better simply declare UIScrollView *scroll; for that purpose.
scroll = secondScroll; // here I intent to pass the content of secondScroll to scroll
But that's not what happens. The scroll views don't magically copy themselves; if you assign one to another, you only assign the pointer.
scroll.frame = firstScroll.frame; // here I assign the frame of firstScroll to scroll
// so that it doesn't take the frame of secondScroll;
So, from now on, if you operate on scroll, it will effect secondScroll.
firstScroll = scroll; // and then pass all the contents of scroll to firstScroll;
And with this, you assign firstScroll to SecondScroll. So firstScroll will now essentially be secondScroll, losing it (leaking memory again and again) and having done nothing useful.
}
What on Earth are you trying to do with this?
Assuming you want to exchange the two views, you should do something like this instead:
BOOL firstScrollVisible = NO; // in -init or whatever
- (void) someAction
{
if (fistScrollVisible)
[self.view bringSubviewToFront:secondScroll];
else
[self.view bringSubviewToFront:firstScroll];
firstScrollVisible = !firstScrollVisible;
}
Try doing the following:
- (void)someAction {
[firstScroll removeFromSuperview]; // So that we do not leave the old firstScroll as a subview
firstScroll = [secondScroll copy]; // Makes a copy of secondScroll and stores it in firstScroll
[self addSubview:firstScroll]; // Add the new firstScroll to the view
}
Important Note(s):
This code will leak memory, as -copy returns an object with a reference count of +1, but we do not release firstScroll. I highly suggest you make firstScroll and secondScroll into nonatomic, retain properties (#property (nonatomic, retain) UIScrollView *firstScroll). If you want more info on this, just ask. Furthermore, you will need to make sure that firstScroll and secondScroll are never garbage values when this code gets run (it will crash) - depending on when and how it is called you may be fine.
If you want to clone a UI element, you use NSCoder (just like the nib file loader does).
NSData *scrollViewData = [NSKeyedArchiver archivedDataWithRootObject:self.firstScroll];
CGRect oldFrame = self.secondScroll.frame;
self.secondScroll = [NSKeyedUnarchiver unarchiveObjectWithData:scrollViewData];
self.secondScroll.frame = oldFrame;
This serializes the UIView and all its subviews into an NSData and then creates a complete copy of those from the NSData.
#jrtc27 Thanks for your answer. I think I've found a solution and it seems to accomplish what I'm looking for. But I'm not sure if it's the right way or not. Here's what I do:
- (void)someAction {
[[firstScroll subviews] makeObjectsPerformSelector:#selector(removeFromSuperView:);//here I sort of blank out the content of the firstScroll
NSArray * array = [secondScroll subviews];//place all subview of the secondScroll in array
int i = 0;
int nrOfSubviews = [array count];
for (i=0;i<nrOfSubviews;i++) { //appoint them one by one to firstScroll
[firstScroll addSubview:[array objectAtIndex:i]];
}
}
downPreView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
downPreView.tag = indexPath;
downPreView.frame = CGRectMake(75,28, 215,60);
downPreView.progress = 0.0;
[cell.contentView addSubview:downPreView];
Now how do I start animating downPreView with a tag of 2, etc?
Thanks!
You can grab a view with the viewWithTag method. Like [cell.contentView viewWithTag:1]
Update
First of all. You cant animate a progressView, you can show it, hide it, and you can edit it's progress level. If you want to edit it's progress, you have to set it like [downPreView setProgress:0.1]. If it's set to 1 the progrees indicator will be at the end.
However if you want just a spinner, you can use UIActivityIndicatorView.
If you cant get the cell, then you can use it's parent view. Like [self.view viewWithTag:1]
After this you have to cast UIProgressView on it to avoid warnings.
UIProgressView *progressView = (UIProgressView *)[self.view viewWithTag:1];
progressView.progress = 0.1;
For more info about UIActivityIndicatorView please see the documentation.
You cannot access an object by it's tag, you have to retain the object if you want access. Say if you have an array of progress views... you can enumerate it and ask
if (object.tag == tagWanted) {
doSomething;
}