Drawing the UIPicker values from multiple components? - iphone

I have the UIPicker setup with multiple components and a button below it. Depending on what the user has chosen with the UIPicker determines which new view will be loaded but I am having trouble determining how to extrapolate the information from the picker itself. Right now I have this method being called when the button is pressed:
- (IBAction) buttonPressed {
if (component:1 == 1 && component:2 == 1) {
//Load the view number 1.
} else if (component:1 == 2 && component:2 == 1) {
//Load the view number 2.
} else {
//Load the view number 3.
}
}
I obviously know that my code is wrong but I hope it gets the point across. I have multiple components and I need to figure out how to use the information that the user is scrolling to on the picker to determine which view to move to. (I know how to load the views, I just commented those in the code to illuminate the problem areas better.)
How do I go about using the pickerViews to extrapolate the information IN the buttonPressed IBAction method?

To respond directly to picker manipulation, use the delegate's pickerView:didSelectRow:inComponent:.
To obtain the view corresponding to user selection, use viewForRow:forComponent:. From that view you can obtain information about what it displays (if it's a UILabel, inspect its text property).
Also, you can obtain this information directly from the data source, referring to the piece of information you would return in the delegate's pickerView:titleForRow:forComponent: or pickerView:viewForRow:forComponent:reusingView:, for the currently selected row and component.

Related

Access to Checkboxes (NSButton) in a Controller View

I have a View with 50 checkboxes (NSButton) in a OS X app. I need to access to each one in code, but the only way I find is to create 50 IBOutlet bindings. The IBOutlet collection is not available in OS X. How can I do?
One way to refer to the 50 checkboxes in your ViewController class (I'm assuming that you use view controllers) is by first retrieving the superview that contains each of the 50 checkboxes. This will be an NSView object. To distinguish between the checkboxes I recommend setting different tag values right in interface builder, but it really depends on what you want to do with the checkboxes. Then, you can use a loop to iterate through each of the subviews in this view, like this:
for i in view.subviews.filter({$0 as? NSButton != nil}).map({$0 as! NSButton}) {
if i.bezelStyle == .regularSquare && !(i.cell as! NSButtonCell).imageDimsWhenDisabled {
print(i.title)
}
}
The if condition basically utilizes two properties of checkboxes to distinguish them from any other type of NSButton.
In this example, I simply printed the title of each checkbox. You can use a switch statement if you want to perform different tasks depending on which checkbox it is. The good thing is that with this method you can have infinitely many checkboxes. The iteration order is left to right, top to bottom.

How to tell UICollectionView to preload a larger range of cells?

I have a UICollectionView which shows images retrieved from the web. They are downloaded asynchronous.
When user scrolls fast, they see placeholders until the cell loads. It seems UICollectionView only loads what is visible.
Is there a way to say "collection view, load 20 cells more above and below" so chance is higher that it loaded more cells while user was looking at content without scrolling?
The idea is to have the VC recognize when a remote load might be required and start it. The only tricky part is keeping the right state so you don't trigger too much.
Let's say your collection is vertical, the condition you want to know about is when:
BOOL topLoad = scrollView.contentOffset.y < M * scrollView.bounds.size.height
or when
BOOL bottomLoad = scrollView.contentOffset.y > scrollView.contentSize.height - M * scrollView.bounds.size.height
in other words, when we are M "pages" from the edge of the content. In practice though, this condition will be over-triggered, like when you're first loading, or if you're testing it on scrollViewDidScroll, you don't want to generate web requests for every pixel of user scrolling.
Getting it right, therefore, requires additional state in the view controller. The vc can have a pair of BOOLs, like topLoadEnabled, bottomLoadEnabled, that are NO until the view is ready. Then, scroll delegate code looks like this:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// compute topLoad and bottomLoad conditions
if (topLoad && self.topLoadEnabled) [self startTopLoad];
similarly for bottom. The load code looks like this, abstractly:
self.topLoadEnabled = NO; // don't trigger more loading until we're done
[self.model getMoreTopStuff:^(NSArray *newStuff, NSError *error) {
// do view inserts, e.g. tableView.beginUpdates
self.topLoadEnabled = YES;
}];
Same idea for bottom. We expect the model to fetch for itself (maybe the model has image urls) and cache the result (then the model has images). As the datasource for the view, the view controller gets called upon to configure view cells. I can just naively ask the model for images. The model should answer either fetched images or placeholders.
Hope that makes sense.
In my opinion you are making the wrong assumption: cells are just views so you shouldn't treat them as model objects. UICollectionView and UITableView are very efficient because they constantly recycle cells so you should think in therms of pre loading content in the business side of things. Create interactor or viewmodel objects and populate your data source with those, then you'll be able to ask those objects to preload images, if you still wish to do so.
A BOOL flag seldom is the answer. I'd rather go for estimating a reasonable page size and fetching images as needed from the cellForItemAtIndePath method.

Display current index of iCarousel in a label which is not a subView in ICarousel

I am using iCarousel in my application,I need to get the current index of the iCarousel and display that index in a label(subView of self.view) which is not a subView of iCarousel.
I am able to get the current index With
int index=iCarousel.currentIndex;
How can i update the index in label.text that every time when the carousel is Scrolled.
In which method i have to write the code to update label.
Whenever you scroll the following delegate will called, so you can update your label here
- (void)carouselCurrentItemIndexUpdated:(iCarousel *)carousel1
{
int index=carousel1.currentIndex;
yourlabel.text = [NSString stringWithFormat:#"%d",index];
}
For using the iCarousel, it is better to study its documentation thoroughly before going into its implementation from iCarousel Documentation.
So for the issue you are facing , there are 2 methods provided in the documentation -
- (NSInteger)indexOfItemView:(UIView *)view;
The index for a given item view in the carousel. Works for item views
and placeholder views, however placeholder view indexes do not match
the ones used by the dataSource and may be negative (see
indexesForVisibleItems property above for more details). This method
only works for visible item views and will return NSNotFound for views
that are not currently loaded. For a list of all currently loaded
views, use the visibleItemViews property.
- (NSInteger)indexOfItemViewOrSubview:(UIView *)view
This method gives you the item index of either the view passed or the
view containing the view passed as a parameter. It works by walking up
the view hierarchy starting with the view passed until it finds an
item view and returns its index within the carousel. If no
currently-loaded item view is found, it returns NSNotFound. This
method is extremely useful for handling events on controls embedded
within an item view. This allows you to bind all your item controls to
a single action method on your view controller, and then work out
which item the control that triggered the action was related to. You
can see an example of this technique in the Controls Demo example
project.
Hope this helps !! :)
A bit late, but for others, the following delegate method works for me (with multiple carousels)
- (void)carouselCurrentItemIndexDidChange:(iCarousel *)carousel
{
NSLog(#"%s", __FUNCTION__);
if (carousel == carousel2) {
NSLog(#"here");
long (or int) currentIndex = carousel.currentItemIndex;
}
}

Duplicating a UITableView with a different data source

I have a UITableView in a ViewController with a custom UITableClass implemented. The table displays different songs that the user can play. The table is populated by a method that pulls data from a server. This method is called in ViewDidLoad.
The user can also tag songs as a 'favorite'. I'd like the user to be able to view all of their 'favorite' tracks in a new `UITableView'. This table should be exactly the same, only with a different data source (only favorited tracks from the server).
How should I implement this? Should I create another method that loads new data to the table with only 'favorited' tracks? Should initialize a new UITableView with the same class and somehow set a different data source or a new ViewController? If so, how?
There will be a slight difference between the two ViewControllers that contain the UITableViews. The original ViewController with all of the tracks will have a button that either changes the datasource or initializes a new UITableView (depending on how it's implemented). The 'favorited' ViewController will have a back button.
I would create a segmented control that has options for "Favorites | All" and when it is switched a BOOL called favoritesOnly or something like that is switched from YES to NO or vice versa. My songs would be kept in an NSArray of NSDictionarys called songsArray and I would use this as my DataSource methods:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if(favoritesOnly)
{
NSInteger count = 0;
for(int n=0; n<[songsArray count]; n++)
if([[[songsArray objectAtIndex:n] objectForKey:#"Favorite"] isEqualToString:#"YES"])
count++;
return count;
}
else
{
return [songsArray count];
}
}
and then for the cells:
-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
{
UITableViewCell *theCell = [tableView dequeueReusableCellWithIdentifier:#"Proto Cell"];
if(favoritesOnly)
{
NSInteger count = -1;
for(int n=0; n<[songsArray count]; n++)
{
if([[[songsArray objectAtIndex:n] objectForKey:#"Favorite"] isEqualToString:#"YES"])
{
count++;
if(count==[indexPath row])
{
//Configure the Cell using [songsArray objectAtIndex:n]
return theCell;
}
}
}
//If you got here there was an error; Error cell?
return theCell;
}
else
{
//Configure cell using [songsArray objectAtIndex:[indexPath row]]
return theCell;
}
}
This way you are using the same set of Data and the same UITableView, you are just using your control to properly delegate how the DataSource displays the information on the UITableView
Now, if you are using CoreData and and NSFetchedResultsController, this is all much much easier.
You can always go the route of just updating a central playlist UITableView, in which case you would just swap the data in your dataSource (in this case, perhaps NSMutableArray *playlist?) and then call [UITableView reloadData]. In this scheme, you avoid the overhead of having multiple views and the trouble of passing data around.
If you're planning to create additional functionality for your favorited song list, a secondary, customized UIViewController might be in order. In this sense, it can be re-used if you decide to have additional song lists. This would be a good solution if you intend to let them do anything additional that you wouldn't want cluttering your main interface with your list(s) such as editing title, song order, etc.
If these two views would be more or less identical, you can just set up a new UIViewController, either pass the new data via property or load it in your init, and then push it onto the view stack. So long as your app is navigation-based, the back button will appear on its own once you push your secondary UIViewController onto the stack. That isn't functionality you need to add on your own. The perks of this include code-reusability, which is a good skill to have as a UI designer and an engineer.
If you just want a read-only view, you can also look into a UIPopoverController with the data that would dismiss once they click away. This solution is not robust in the least and shouldn't be used if you intend the user to be doing anything more than tapping an entry, or if you expect your datasets to get big.
When planning your UI and flow, just make sure you think of what directions you might take it in the future. As mentioned in another answer, how you store your data makes a difference, as well as how you intend to make calls to your server (button clicks? after a set time?)
how do you store your data from the server? if you use CoreData (or MagicalRecord, which I can recommend) that having a fetched results controller with a different argument would be the only change you need.....
Ah, link to MagicalRecord: MagicalRecord
when you are tagging song as a favorite send one flag as a favorite to the web-service and then call another web service in the favorite view controller and make new table view with the same custom class and view the new source coming from server... and if you are storing in sq-lite or using core data make one column extra as favorite and call it in favorite view controller and load it with different data-source.
I would have a refresh method, and a button that switches between "show all" and "show favorites".
Basically, if the button is clicked, switch to the opposite group of objects, and update the text on the button accordingly. The table will always load an array called "tableDataArray" in my example, and you'll get row counts and such from the length of it.
Like...
-(IBAction)refresh {
if ([faveButton.text isEqualToString:#"Show All"]){
tableDataArray = favoriteArray;
[faveButton setText:#"Show Favorites"];
}
else {
tableDataArray = allSongsArray;
[faveButton setText:#"Show All"];
}
[tableView reloadData];
}
Simplest way to do it would be to have two instances of the same view controller. Each instance will have its own data source, one with all the songs, another with only the favorites.

iPhone: Cells order changes in UITableView Section After Call to reloadSections: Method

I have a table with two sections. A segmented control in first sections changes which rows are displayed in the second section. My problem is that the order of the rows and which row are displayed in the second section shifts improperly upon each subsequent press of a button in the segmented control.
I allow a user to add a product to a shopping list 3 different ways: by name, by barcord and by taking a picture with a camera. I have 3 buttons in a UISegmentedControl so the users can select which method to use. Depending on which segement the user selects the fields in the second segment should change to show cells relevant to that method.
Section 0:
0 row with segmented control showing name, barcode and camera buttons
Section 1:
// button zero, name button
0 row with textfield
1 row with textfield
or
// button 1, barcode button
0 row with textfield
or
// button 2, camera button
// shows camera view
I've put placeholders in each UITextField.
Each time a button in the segmented control is clicked, I call a pickOne: method that updates the tablevew. In that method, I construct a NSIndexSet with NSRange of (1, 1), and then I call the reloadSections: method of the UITableViewController with the NSIndexSet as a parameter.
When the view appears for the first time, everything is ok but when I click the buttons repeatedly, the order of the cells changes. Cells containing the two textFields for the button0 and the new placeHolders are written over the old ones.
Worse, sometimes when I click on button 0, it shows me only the second cell of the two cells.
My detailed code can be seen here http://pastebin.com/9GwMpCS9
I'm seeing a couple of problems.
The first big one is that you're adding subviews into the cells bypassing the contentView. Subviews in predefined styles are broken up into different parts depending on their roles. You have the editing control, the content view, and the accessory view. While you can add directly to the cell's view, there'll be odd behavior because the predefined cells are expecting the content to be in the content view.
I think what's causing your problem is that you're adding subviews every time a cell is decorated but you never remove them. When a cell is dequeued there's no guarantee that everything is restored to the pristine new condition as if it was alloc'ed. Things like custom accessory views that aren't removed can be left behind. I'm pretty sure that's happening. You're collecting visual trash on cells that should be clean.
I believe your problem is here.
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//....
if(addMode == NAME) {
if(indexPath.row == 0) {
[cell addSubview:nameTextField];
}
else if(indexPath.row == 1) {
[cell addSubview:categoryTextField];
}
}
else if(addMode == BARCODE) {
[cell addSubview:barcodeTextField];
}
else if(addMode == SCAN){
//Scanning mode
}
}
return cell;
}
This because the table always shows has having two sections, this method is always called for section 1. Regardless of the input type selected, it creates or dequeue a cell and returns it. Whenever addMode==SCAN, it randomly dequeues one of the previously used cells for the name or barcode addMode and returns that.
I suggest that you remove the SCAN logic from the table altogether or that you create a row for the camera.
I think the latter the best UI. With the first two buttons, the users is presented with a choice in the second section. You should maintain that pattern with the camera choice. Just have a cell that displays a button that evokes the camera. Yes, it adds a second step but establishes a kinetic pattern for the user: Select input type in section one then select an appropriate cell in section two. The user shouldn't have to stop and think each time whether they need to hit one of the rows in section two or not. They should just do so automatically.