I want to make horizontal paging in my app.
I have big text, which placed in UITextView, but I want to make horizontal paging, like iBooks or bookmate.
Do you have any solution or idea?
Have a look at the new (iOS 5) class UIPageViewController. For the iBooks page curl effect, try using
[controller initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
options:nil];
You can then set the view controllers using setViewControllers:direction:animated:completion:. For more reference for this class, visit the UIPageViewController Class Reference.
Use this handy class called PagedView which manages a paging scroll view as well as view loading/unloading and reuse for you.
You have to implement two delegate methods to get it working:
- (NSUInteger)numberOfPagesInPagedView:(PagedView *)view;
- (UIView *)pagedView:(PagedView *)view viewForPageAtIndex:(NSUInteger)page;
They will look familiar if you've ever used UITableView. In numberOfPagesInPagedView: you just have to return the number of pages you want to display.
- (NSUInteger)numberOfPagesInPagedView:(PagedView *)view
{
// return the number of pages in the paging view
return 10;
}
In pagedView:viewForPageAtIndex: you have to return a view for a specific page index. You can reuse the views by sending the dequeueReusableViewWithIdentifier: message to the paged view.
- (UIView *)pagedView:(PagedView *)pagedView viewForPageAtIndex:(NSUInteger)page
{
static NSString *reuseIdentifier = #"PageIdentifier";
UIView *view = [pagedView dequeueReusableViewWithIdentifier:reuseIdentifier];
if(view == nil) {
view = [[[MyPageView alloc] initWithFrame:view.bounds] autorelease];
}
// add contents specific to this page index to the view
return view;
}
In order to get view reuse working, your UIView subclass (MyPageView) should conform to the ReusableObject protocol and implement reuseIdentifier (in this case you would return #"PageIdentifier").
I have used this class in a number of projects and it works pretty well.
UIScrollView with pageEnabled turned on?
You can use a UIWebView (subclass of UIScrollView) for your horizontal paging needs.
For it to work, you'd need to split the NSString that you use to store your text, depending on the size (and height) of your web view.
Store the split NSString into an NSArray, and then depending on user swipe, load up the correct 'page' (array index) and display with animation.
Hope that helps.
Related
I have made a view in the application that shows a UITableView. It will be full of results (obviously) almost all the time, however when it does not have any results I want to show another view that inform the user about how he/she could populate the table.
I want to design that view in the interfacebuilder. I will have to check in the code whether the datasource is empty or not to toggle between the two different nibs. How do I instantiate and configure a view made in Interfacebuilder?
The easies way to do this is by adding the view in xib normally and make it visible
Design your both views, the table view and the other view, give the tableView a tag of 111 for example and give the otherview another tag 222 for example
Now in viewDidLoad
Get both the views
UIView *noDataView = [self.view viewWithTag:222];
UITableView *tableView = [self.view viewWithTag:111];
//Hide both of them or only the noDataView until you know if you have data from the dataSource or not
Check for your data source
//hasElements do you have any element to show?
if(hasElements)
{
noDatView.hidden = YES;
tableView.hidden = NO;
}
else
{
noDatView.hidden = NO;
tableView.hidden = YES;
}
You can load nib file based on condition.You can write category as follows:
self.view = (UIView *)[self loadNib:#"SecondView" inPlaceholder:self.view];
- (UIView *)viewFromNib:(NSString *)nibName
{
NSArray *xib = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil];
for (id view in xib) { // have to iterate; index varies
if ([view isKindOfClass:[UIView class]]) return view;
}
return nil;
}
- (UIView *)loadNib:(NSString *)nibName inPlaceholder:(UIView *)placeholder
{
UIView *nibView = [self viewFromNib:nibName];
[nibView setFrame:placeholder.frame];
self.view = nibView;
//[self.view insertSubview:nibView aboveSubview:placeholder];
//[placeholder removeFromSuperview];
return nibView;
}
The other answers give you possible technical solutions but I would propose that if you are using the standard Apple design guidelines, you probably don't even need to worry about it. For instance, somewhere on your screen you should have a bar button item with the identifier "Add" (which shows the plus icon). Then rather than giving a long (often poorly localised) description of how to add items, just have a header for an empty section which says "No items" replacing items with whatever pluralised noun is appropriate for your table's items. For example, for an Archery related app I am working on:
Notice how the Edit button is currently disabled too, thus no explanation is needed as the only thing they can do at this point is tap the Add button (screenshots on the Appstore will have shown them what they can expect to see after this point).
I'm building an app which consists of different views which are closely related to each other. So far, I only have one UIViewController which controls these different views. View 1 and 2 share the same background, for instance, and the transition between view 1 and 2 is a custom animation.
My problem is that both view 1 and 2 have an UIScrollView. My UIViewController is their delegate and I could have the following scrollViewDidScroll to distinguish between the two scrollviews:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView.tag == 1)
//handle a
else if (scrollView.tag == 2)
//handle b
else if (scrollView.tag == 3)
//handle c
}
As a lot happens with scrollView 1 and different things happen with scrollView2, the code will become really messy. Ideally, I'd like to define in a separate file what happens if scrollView1 is scrolled etc.. Yet I don't want to have another UIViewController as then transitions become more difficult. I don't have a NavBar or ToolBar, so neither UINavigationController nor UITabBarController would work very well in my case.
What should I do?
I posted a similar question here.
If you don't want two view controllers, just create a separate delegate for each scroll view. Make it an NSObject which conforms to UIScrollViewDelegate and create it at the same time as the scroll view.
Seems to combine the results you seek: one view controller, but encapsulated scroll view code.
You could have a base controller class that handles the common functionality. Each different controller can inherit from this and override with their specific functionality as required.
Aka the template pattern
Edit
To expand. You say you want only one view controller. So you should create a separate class to handle the individual functionality. The View Controller has a base class pointer which gets swapped around according to the current view.
In pseudo code :
class BaseFunctionality
-(void) handleDidScroll {}
end
class ScrollViewAFunctionality : BaseFunctionality
-(void) handleDidScroll {
// Lots of interesting technical stuff...
}
end
class ScrollViewBFunctionality : BaseFunctionality
-(void) handleDidScroll {
// Lots of interesting technical stuff...
}
end
class TheViewController : UIViewController
BaseFunctionality *functionality;
-(void) swapViews {
// Code to swap views
[this.functionality release];
if (view == A)
this.functionality = [[ScrollViewAFunctionality alloc] init]
else if ( view == B)
this.functionality = [[ScrollViewBFunctionality alloc] init]
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[this.functionality handleDidScroll];
}
end
I have 5 different views and when I tap the button, I want to push one of 5 views randomly. However, I do not want to make 5 different controllers for each views. Do I have a chance to put them in one controller? If so, how?
You can have as many views as you want in a single UIViewController subclass. You can create them all in Interface Builder as well, in the one .xib file for your UIViewController class (it might get a bit hard to see, though, better to lay out each UIView in its own .xib). You can present them in any combination you want. Assuming you want to just show one view at a time, you can do this:
In your viewDidLoad method, start out showing the initial view, and in your class keep track of which is the current view:
...
#property(nonatomic, retain) UIView *currentView;
...
- (void)viewDidLoad
{
[self.view addSubview:self.defaultView];
self.currentView = self.defaultView;
}
Then to switch to a particular other view do this:
- (void) switchToView:(UIView *)newView
{
[self.currentView removeFromSuperView];
[self.view addSubview:newView];
self.currentView = newView;
}
}
Or you can show them all at one time: just [self.view addSubview:theView];
You have to set the tags of all the views, and then use
int r = arc4random() % 5;
method to find the random number. Use this newly generated number to check the tag, in your selector
Just change the view outlet of the controller on the action of the button. Use random numbers to select which view. Ouh and - (void)setNeedsDisplay :)
I'm trying to create this sort of "pop up action sheet" view similar to the in-call view in iPhone's phone app.
I believe this is a custom view, since I can't seem to find this in any apple references. But somehow the Google app and Discover app both have this view and look awfully similar (I've attached the images below).
So is there some kind of library/tutorial/sample code out there that can help me make something like this?
Thanks.
alt text http://a1.phobos.apple.com/us/r1000/018/Purple/e1/23/02/mzl.uiueoawz.480x480-75.jpg
(source: macblogz.com)
alt text http://ployer.com/archives/2008/02/29/iPhone%20infringes%20call%20display%20patent-thumb-480x799.png
They all look suitably different to be custom views to me. If you just want a control like this for a single view (i.e. not a more flexible configurable container type control) it should be relatively quick & easy to knock it up in xcode & IB. I've done similar things in my apps. Steps I would take are as follows:
1) create an empty NIB file and design your control there by using UIView, UIImageView, UIButton controls etc.
2) Create a new ObjC class derived from UIView
3) Ensure the 'root' UIView object in the NIB has class type matching your ObjC UIView derived class
4) Attach IBOutlets & IBAction event handlers to your class and wire up all the button events ('Touch up inside') to your class event handler methods in IB.
5) Add a static factory function to your class to create itself from the NIB. e.g.
// Factory method - loads a NavBarView from NavBarView.xib
+ (MyCustomView*) myViewFromNib;
{
MyCustomView* myView = nil;
NSArray* nib = [[NSBundle mainBundle] loadNibNamed:#"MyCustomViewNib" owner:nil options:nil];
// The behavior here changed between SDK 2.0 and 2.1. In 2.1+, loadNibNamed:owner:options: does not
// include an entry in the array for File's Owner. In 2.0, it does. This means that if you're on
// 2.2 or 2.1, you have to grab the object at index 0, but if you're running against SDK 2.0, you
// have to grab the object at index:1.
#ifdef __IPHONE_2_1
myView = (MyCustomView *)[nib objectAtIndex:0];
#else
myView = (MyCustomView *)[nib objectAtIndex:1];
#endif
return myView;
}
6) Create and place onto your parent view as normal:
MyCustomView* myView = [MyCustomView myViewFromNib];
[parentView addSubview:myView];
myView.center = parentView.center;
With regard to the event handling, I tend to create just one button event handler, and use the passed id param to determine which button is pressed by comparing against IBOutlet members or UIView tags. I also often create a delegate protocol for my custom view class and call back through that delegate from the button's event handler.
e.g.
MyCustomViewDelegate.h:
#protocol MyCustomViewDelegate
- (void) doStuffForButton1;
// etc
#end
ParentView.m:
myView.delegate = self;
- (void) doStuffForButton1
{
}
MyCustomView.m:
- (IBAction) onButtonPressed:(id)button
{
if (button == self.button1 && delegate)
{
[delegate doStuffForButton1];
}
// or
UIView* view = (UIView*)button;
if (view.tag == 1 && delegate)
{
[delegate doStuffForButton1];
}
}
Hope that helps
I'm implementing tableView viewForHeaderInSection where I return a nib loaded UITableViewCell.
I've simplified it for the purpose of this question.
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UIView* header = [CellFactoryController newSectionHeader];
header.text = "Some Text Depending on the section"
return header;
}
Unfortunatly this is very slow because I sometimes have 50+ headers which all get loaded from nib all at once when the table draws , even though those headers are not in view.
Is there any realistic way of me being able to clone UIViews and thus clone the header in this example? Or is the only way for me to just create the headers UIView hard coded?
Thanks
I was reading the doco on the UINib class the other night and it was talking about how it caches the nibs internal structure so repeated requests execute faster. That might be a suitable option for you.
Well, I don’t know if you still need an answer to this question, but… what the heck. Let’s do this. I’m going to make some assumptions, but if you’re having performance problems the only way to really solve this is going to be to test it, and without more knowledge of your views’ complexity, we can’t really get reliable metrics. So I’m going to assume that you’ve tried a few things and throw something out there that you maybe haven’t tried.
When you load a view out of a nib, it unarchives it from a file. If that loading is happening 50 times, that could go a long way towards your performance problems. So what else can we do? Well, in your table view controller class, make a new instance variable of type NSData. Then, in your -init method (or other designated initializer), load a view in from a property list:
- (id)init
{
self = [super init];
if (self) {
// Other initialization code.
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"MyView"
ofType:#"plist"];
myData = [[NSData alloc] initWithContentsOfFile:filePath];
}
return self;
}
OK, so you have this property list loaded into an NSData. Now, the rest should be clear:
- (UIView *)tableView:(UITableView *)tableView
viewForHeaderInSection:(NSInteger)section
{
UIView *view = [NSKeyedUnarchiver unarchiveObjectWithData:myData];
return view;
}
My goal here was to keep the fully-instantiated view in memory the whole time. But then I thought, “how is he going to create that plist?” So I modified it a bit. Instead of loading the data from a file, create your view in code:
- (UIView *)tableView:(UITableView *)tableView
viewForHeaderInSection:(NSInteger)section
{
UIView *headerView;
if (myData == nil) {
headerView = [[UIView alloc] init];
// Configure your view, but only that part that isn’t customized.
myData = [[NSKeyedArchiver archivedDataWithRootObject:headerView] copy];
} else {
headerView = [NSKeyedArchiver unarchiveObjectWithData:myData];
}
// Now customize the view for your particular section.
return headerView;
}
That should give you the flexibility of creating the view in code, but hopefully also keep it in memory and cache it for you. Try it out!
You could, potentially, break up your views into separate .xib files to speed loading, but (as you said) your best bet is probably going to be to create those views procedurally. Outside of quickie sample code, .xib files are generally reserved for very high-level layout of containers needed for an entire app or a full-screen view. The layout and drawing detail-oriented elements— such as the cells, headers, and footers in table views— is typically better handled procedurallly.