I'm still pretty new at this, so bear with me. I thought I would be able to load a Xib in a ScrollView, as I have seen apps that seem to do this, but we're are talking two different classes. But I'll ask anyway - Is there any practical way to have a scrollView with a static Xib over the top, where buttons defined in the UI don't move while a view underneath does. I'm sure it's easily doable in cocos2d, but for what I want to do, it's a bit overkill.
--- Edit ---
At the risk of embarrassing myself, I tried both possible solutions. Adding a button grammatically adds a button that moves when I scroll. Adding the nib seems to keep the scroll screen from scrolling. Here's the code, without trying to add any buttons everything works fine.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(#"View Loaded");
[mdm setMapSetupInfoWithRows:60 columns:90 cellSize:32];
[mdm initMapDataWithOriginsUsingCenter:TRUE];
NSLog(#"MapViewContoller.mapArrayCount = %d",[[mdm mapArray]count]);
// create the MapView with the screen size create by MapDataManager
mapView = [[MapView alloc] initWithFrame:[mdm mapRect]];
// Create the UIScrollView to have the size of the window, matching the window (screen) size
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:[mdm windowRect]];
[scrollView setBounds:[mdm windowRect]];
// Tell the scrollview how big it is and set other options
[scrollView setContentSize:[mdm mapRect].size];
[scrollView setBounces:NO];
[scrollView setMinimumZoomScale:.5];
[scrollView setMaximumZoomScale:10];
[scrollView setDelegate:self];
[scrollView setBackgroundColor:[UIColor darkGrayColor]];
//add the MapView as a subview of the scrollView
[scrollView addSubview:mapView];
//add the scrollView to the current one....
[[self view] addSubview:scrollView];
[[NSBundle mainBundle] loadNibNamed:#"MapViewController" owner:self options:nil];
[self generNewMap];
}
Anything else I'm trying to do wrong? after looking at this more it does seem doable.
You should set things with a hierarchy like this.
UIViewController
UIScrollView
Static Buttons etc
Then in interface builder, or code, just add the static buttons etc to the self.view.
I do everything in code so it would look something like
-(void)viewDidLoad {
//add scrollview
appScroll = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
appScroll.pagingEnabled = YES;
[appScroll setCanCancelContentTouches:NO];
appScroll.bounds = CGRectMake(0, 0, 320, 480);
appScroll.contentSize = CGSizeMake(1600, 480);
[appScroll setScrollEnabled:YES];
appScroll.bounces = NO;
appScroll.showsHorizontalScrollIndicator = NO;
appScroll.showsVerticalScrollIndicator = NO;
appScroll.clipsToBounds = YES;
appScroll.delaysContentTouches = YES;
appScroll.center = (CGPoint){ 160, 240 };
[appScroll setBackgroundColor:[UIColor darkGrayColor]];
[self.view addSubview:appScroll];
//back
backBut = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"back.png"]];
backBut.center = (CGPoint){ 40, 430 };
backBut.transform = CGAffineTransformMakeScale(0.3,0.3);
[backBut setUserInteractionEnabled: YES];
[self.view addSubview: backBut];
}
The trick is to specify the owner when loading the XIB. For example:
Define an IBOutlet on your UIViewController
#property (nonatomic, strong) IBOutlet UIView* myScrollViewContent
Create a XIB, specify the owner as your UIViewController class
Wire the components defined in the XIB to the outlets defined in the UIViewController class
Then in code do this:
//Because the owner is 'self' the bundle loader will inject any properties defined . .
//. . . . and wired in the XIB to the owner's outlets
[[NSBundle mainBundle] loadNibNamed:#"MyXibName" owner:self options:nil];
//Now do something with self.myScrollViewContent - ie add it to the scroll view.
Custom Scroll-view sub-class
If you wanted should be able to use the same approach by creating a cusom scroll-view subclass and specifying an outlet there. . . The UIView would be loaded directly onto the sub-class, then. . . (you'd still have to add it as sub-view).
For more complex requirements, I personally like to build my views with pure code, but it is possible to arrange things neatly with XIBs.
Related
I have a UIViewController that has its UI elements setup in storyboard, and things are showing up fine. Now I created a new UIView in separate xib, .h and .m files, I'll call it "overlay"; then I present that overlay which should cover everything below, so that only the overlay can be seen:
// in controller.m
OverlayView *overlayView = [[OverlayView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
overlayView.layer.zPosition = 1000;
[self.view addSubview:overlayView];
The overlay appears in a weird position (the top of it is some 50px away from the bottom of the navigation bar). What's more, apart from the background of the overlay, no elements in the overlay can be seen. It's just a blank red canvas. I have double checked that the elemenets' alpha values are 1, they are set to be not hidden, and they are also set explicitly in the initWithFrame of the overlay:
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor redColor]; // red canvas shows up
self.title.textColor = [UIColor blackColor]; // title can't be seen; why not?
self.body.textColor = [UIColor blackColor]; // body can't be seen; why not?
}
return self;
}
The reason I don't directly put overlay in the controller's xib and then simply change it's hidden property is that this overlay is to be used by multiple controllers, and I'd like to re-use it, thus putting it as a separate view.
What am I doing wrong?
The parent view (view controller's view) is responsable to set the child view's frame. If you use Auto Layout you need to use setTranslateAutoresizingMaskIntoConstraints to NO and add the desired constraints in code. Keep in mind that the child's frame will be set after viewDidLayoutSubviews is called.
If you don't use Auto Layout just set the frame using setFrame method in viewWillAppear.
It turns out I had to load the nib from within the view's own initWithFrame.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
id mainView;
if (self) {
NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:#"MyOverlayView" owner:self options:nil];
mainView = [subviewArray objectAtIndex:0];
}
return mainView;
}
- (void)awakeFromNib {
[super awakeFromNib];
}
The code above fixed it.
I am using this control (RDVCalendarView) and I want to little customize it. I want to change height of calendar so it wouldn't be height as whole controller but little smaller. So in loadView I change code to this:
CGRect applicationFrame = [[UIScreen mainScreen] applicationFrame];
applicationFrame.size.height = 200;
_calendarView = [[RDVCalendarView alloc] initWithFrame:applicationFrame];
[_calendarView setAutoresizingMask:UIViewAutoresizingNone];
[_calendarView setSeparatorStyle:RDVCalendarViewDayCellSeparatorTypeHorizontal];
[_calendarView setBackgroundColor:[UIColor whiteColor]];
[_calendarView setDelegate:self];
self.view = _calendarView;
In whole initWithFrame method of RDVCalendarView there is correct size of height which I set. But after viewWillAppear there is layoutSubviews call and has 504 size of height. I don't know what happens but it looks like height is autoresizing to height of controller. I just don't know where it could be. Thanks for help
Usually you don't directly set the frame of a UIViewController's main view, see https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/AdoptingaFull-ScreenLayout/AdoptingaFull-ScreenLayout.html
Instead, you simply add the view you want to resize as a subview to your view controller's root view. To do that, simply change the method from loadView to viewDidLoad. At that point, the system has already created a default UIView for you and assigned it to the view controller's view property.
Change your code to this:
- (void)viewDidLoad {
CGRect calendarFrame = self.view.frame;
calendarFrame.size.height = 200;
_calendarView = [[RDVCalendarView alloc] initWithFrame:calendarFrame];
[_calendarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
[_calendarView setSeparatorStyle:RDVCalendarViewDayCellSeparatorTypeHorizontal];
[_calendarView setBackgroundColor:[UIColor whiteColor]];
[_calendarView setDelegate:self];
[self.view addSubview:_calendarView];
}
That should do the trick.
I tried using custom views for the SVPullToRefresh but I've stumbled upon weird issue. I've made the demo project to illustrate the issue: https://github.com/gaks/SVRefreshProblem
The project is simply one UITableViewController with a TableView and the xib file with custom refresh bar view. The SVPullToRefresh is added to the tableView via the IBOutlet in the viewDidLoad: https://github.com/gaks/SVRefreshProblem/blob/master/SVRefreshProblem/DemoViewController.m
There is also a code that loads the RefreshBarView from the xib file and sets it as a custom view for SVPullToRefresh (for 'loading' state only for illustration):
RefreshBarView* refreshBarView = [[[NSBundle mainBundle] loadNibNamed:#"RefreshBarView" owner:self options:nil] objectAtIndex:0];
refreshBarView.bounds = CGRectMake(0, 0, 320, 60);
[[tableView pullToRefreshView] setCustomView:refreshBarView forState:SVPullToRefreshStateLoading];
The issue is that when you pull the table view to trigger the refresh it goes into infinite loop and eat-all-available-memory state after calling [self layoutIfNeeded] in the - (void)setState:(SVPullToRefreshState)newState. As far as I was able to debug it keeps calling - (void)layoutSubviews over and over.
What's weird is that when you open the xib file: https://github.com/gaks/SVRefreshProblem/blob/master/SVRefreshProblem/SVPullToRefresh/RefreshBarView.xib
... and remove the label from the view - it works just fine (except you have an empty view).
What am I doing wrong?
Loading from nibs is for ViewControllers, not views proper. You can try to do this programmatically which will work :
UIView *v = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 60)];
[v setBackgroundColor:[UIColor grayColor]];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 60)];
[label setText:#"asdf"];
[v addSubview:label];
[[tableView pullToRefreshView] setCustomView:v forState:SVPullToRefreshStateLoading];
Try to turn off "Use Auto Layout" in your nib.
Despite the number of similar posts on this, I still am having trouble making this work correctly.
I have a two UIViewControllers which I have designed in IB with UILabels and imageviews, etc.
I would like to add these to a scroll view so they can be paged between.
I have outlets defined connecting each of the scrollViewcontrollers with their elements in IB, however, the controllers themselves, I am programatically creating from within the master view controller in viewDidLoad. After creating them, I assign some values from the master view controller than add it as a subview to the scrollViewController. However nothing displays. From the debugging i have done, when I set a break point after assigning the values, I can see that the elements are nil. They should be loaded from the xib file, but it does not seem to be read. Even as a test, I tried initializing one of the elements (eg. UILabel) and setting it to that sub view controller, but it nothing would display. Here is some code to explain a bit better.
In the master view controller
detailControllerA *tempA = [[detailControllerA alloc] initWithNibName:#"detailOverviewA" bundle:nil];
self.overviewA = tempA;
[tempA release];
self.overviewA.someLabel.text = #"Some Text";
detailScrollView.contentSize = CGSizeMake(scrollWidth, scrollHeight);
detailScrollView.pagingEnabled = YES;
[detailScrollView addSubview:self.overviewA.view];
In the detailControllerA implementation I set the frame in loadView:
-(void)loadView {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 90)];
self.view = view;
[view release];
}
I also have the labels, etc defined with IBOutlets in detailControllerA.h and implemented to the elements in the xib file.
Any ideas why the xib is not loading correctly when created programatically?
Try in this way:
NSUInteger scrollContentCount = 0;
for (NSUInteger arrayIndex = 0;
arrayIndex < [contents count];
arrayIndex++) {
// set scorllview properties
CGRect frame;
frame.origin.x = self.mScrollView.frame.size.width * arrayIndex;
frame.origin.y = 0;
frame.size = self.mScrollView.frame.size;
myScrollView.autoresizingMask = YES;
// alloc - init PODetailsView Controller
myController = [[MyController alloc] initWithNibName:#"MyController" bundle:[NSBundle mainBundle]];
[myController.view setFrame:frame];
// add view in scroll view
[self.myScrollView addSubview:myController.view];
scrollContentCount = scrollContentCount + 1;
}
// set scroll content size
self.myScrollView.contentSize =
CGSizeMake(self.myScrollView.frame.size.width * scrollContentCount,
self.myScrollView.frame.size.height);
}
Don't release the content controller object which is in for-loop.
Set your scrollView contentSize according to your requirement.
Hope this would be helpful to you.
Interestingly enough, the -(void)loadView was the problem.
Since the view is being generated in interface builder (with the frame settings), when loadView fires, it was creating a new view, blowing away the one created in interface builder.
My app is building purely programmatically on UITabBarController above a UINavigationController, which are both declared in my AppDelegate. Inside my navigationcontroller, I'm showing a UIViewController, a custom class. This custom class should show a custom UIView in a UIScrollView and a UIPageControl.
Heres my problem:
self.view seems to create an EXC_BAD_ACCESS error when I call it without declaring self.view = [[UIView alloc] init] (or similar). I was wondering if this was a problem with -(void) loadView but seems like it produces the same error in -(void)viewDidLoad. So I basically had to use self.view = scrollView to even show my scrollView, considering [self.view addSubview:scrollView] produced an error. My UIPageControl should stay on the page all the time, and actually be another part of the view than the UIScrollView. So I tried to add a container-view like this
Code:
container = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0,0,0,0)];
scrollView.pagingEnabled = YES;
scrollView.showsVerticalScrollIndicator = NO;
scrollView.showsHorizontalScrollIndicator = NO;
// EDIT: ofcourse, I'm also resizing the frame with [scrollView setContentSize:] later, but this is farfetched code to add here.
[container addSubview:scrollView];
self.view = container;
Unfortunately, it seems that I don't get any result at all, and what appears is just an empty view. However, if I add a UILabel or similar, it shows:
[container addSubview:[[UILabel alloc] initWithFrame:CGRectMake(50,50,50,50)]]; // entered above or beneath the addSubview:scrollView line.
My question is: Why doesn't my scrollView appear in the UIView container?
I've noticed that some tutorials say that scrollView must have a delegate, and I agree with the logic - however I can't seem to find out how I set that delegate when I am in my CustomUIViewController-class instead of my AppDelegate.
after you change the UIScrollView size you should use:
[scrollView setNeedsDisplay:YES];
also you implement Delegates the same way you do in other classes:
.h:
#interface MyClass : NSObject <UIScrollViewDelegate>
Okay, the problem seemed to be the initialization - I didn't realize that frame and content was two different things. Seems like the frame that is initializing the view should be whatever size the view should fill, while content is the actual content of whatever should be scrolled. So when I was having problems with user interaction, it was really this.
The problem of why it didn't show in the first place was (stupid.) that the frame was initially, and never changed from, 0,0 so I really lied in my first post.
Thanks to UIScrollView and PageControl: space between views who solved my problem with user interaction.
My steps was to backtrace from self.view:
NSLog(#"%f\n%f",
((UIScrollView*) [[self.view subviews] objectAtIndex:0]).frame.size.width,
((UIScrollView*) [[self.view subviews] objectAtIndex:0]).frame.size.height);
when I realized these were 0 and 0, fixing the problem wasn't too hard :) thanks though, for your efforts Kristian.