how to add a uiscrollview in IB - iphone

how is it possible to add an image to be set inside an uiscrollview using just interface builder and no code. i tried simply adding an image into the scrollview and it didnt work. i know this is a really simple answer however i didnt find any IB related help on achieving this

You can add the image to the scroll view in Interface Builder, but in order for it to scroll (even if the image is larger than the scroll view) you need to manually set the scroll view's contentSize property in code, as I am not aware of any way to set this property in Interface Builder. In your view controller's viewDidLoad method, you can add something like:
scrollView.contentSize = imageView.frame.size;
Only one line of code, shouldn't be too much trouble.

Alternatively as other people have suggested you can create a custom subclass of UIScrollView. Then overided initWithCoder:
- (id)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
if ([self.subviews count] > 0) {
UIView* subview = [self.subviews objectAtIndex:0];
self.contentSize = subview.frame.size;
self.alwaysBounceHorizontal = NO;
self.alwaysBounceVertical = YES;
self.showsHorizontalScrollIndicator = NO;
self.scrollEnabled = YES;
}
}
return self;
}
Then set the class of your scroll view in IB to your custom subclass. This will mean the content size is set automatically.
This only works if you have one subview in the scroll view. Otherwise add create a new 'container view', put everything in it, then add this as the only subview to the scroll view.

Related

iOS 7 Table view fail to auto adjust content inset

I am transiting my project to iOS7. I am facing a strange problem related to the translucent navigation bar.
I have a view controller and it has a tableview as subview (let's call it ControllerA) . I init a new uinavigationcontroller with the controllerA and present it modally using presentviewcontroller. The presented view controller's table view is blocked by the navigation bar. I set the automaticallyAdjustsScrollViewInsets to YES but the result did not change.
I knew I can set the edgesForExtendedLayout to UIRectEdgeNone, but it will make the navigation bar no more translucent.
After that, I tried to create a new view controller for testing. It contains almost the same elements. But the result is much different. The table view content does not get blocked.
Conclusion
Two View Controllers' automaticallyAdjustsScrollViewInsets set to YES
The project is not using storyboard
The first one is created at Xcode 4.6, The second one is newly created on Xcode 5
I have compared two classes xib and code, not much different
I have found the answer on apple developer forum.
There are two different case.
The first one, the view controller added is a UITableViewController.
And the issue should not be appeared since apple will auto padding it.
The second one, the view controller is NOT a UITableViewController.
And in the view hierarchy, it contains a UITableView. In this case, if the UITableview(or ScrollView) is the viewController's mainview or the first subview of the mainview, it will work. Otherwise, the view controller doesn't know which scroll view to padding and it will happen the issue.
In my case, the view controller is the second one. And there is a background image view as the first subview of the main view. So, it fails.
Here is the Apple developer forum link (need developer account to access):
https://devforums.apple.com/message/900138#900138
If you want the view to underlap the navigation bar, but also want it positioned so the top of the scrollview's content is positioned below the navigation bar by default, you can add a top inset manually once the view is laid out. This is essentially what the view layout system does when the top-level view is a scroll view.
-(void)viewDidLayoutSubviews {
if ([self respondsToSelector:#selector(topLayoutGuide)]) {
UIEdgeInsets currentInsets = self.scrollView.contentInset;
self.scrollView.contentInset = (UIEdgeInsets){
.top = self.topLayoutGuide.length,
.bottom = currentInsets.bottom,
.left = currentInsets.left,
.right = currentInsets.right
};
}
}
Based on Tony's answer I was able to get around this problem programatically with temporarily sending the table view to the back, let the adjustments be made and then send the background view back to the back. In my case there is no flickering to this approach.
In the View Controller:
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
[self.view sendSubviewToBack:self.tableView];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self.view sendSubviewToBack:self.backgroundView];
}
Obviously if there are other subviews on self.view you may need to re-order those too.
There's probably too many answers on this already, but I had to take Christopher's solution and modify it slightly to support view resizing and allowing the content inset to be changed in a subclass of the UIViewController.
#interface MyViewController ()
#property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
#property (assign, nonatomic) UIEdgeInsets scrollViewInitialContentInset;
#end
#implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setScrollViewInitialContentInset:UIEdgeInsetsZero];
}
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
if (UIEdgeInsetsEqualToEdgeInsets([self scrollViewInitialContentInset], UIEdgeInsetsZero)) {
[self setScrollViewInitialContentInset:[self.scrollView contentInset]];
}
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
UIEdgeInsets scrollViewInset = [self scrollViewInitialContentInset];
if (UIEdgeInsetsEqualToEdgeInsets(scrollViewInset, UIEdgeInsetsZero) {
if ([self respondsToSelector:#selector(topLayoutGuide)]) {
scrollViewInset.top = [self.topLayoutGuide length];
}
if ([self respondsToSelector:#selector(bottomLayoutGuide)]) {
scrollViewInset.bottom = [self.bottomLayoutGuide length];
}
[self.scrollView setContentInset:scrollViewInset];
}
}
#end
To explain the point:
Any subclass of MyViewController can now modify the contentInset of scrollView in viewDidLoad and it will be respected. However, if the contentInset of scrollView is UIEdgeInsetsZero: it will be expanded to topLayoutGuide and bottomLayoutGuide.
#Christopher Pickslay solution in Swift 2:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let topInset = topLayoutGuide.length
inTableView.contentInset.top = topInset
inTableView.contentOffset.y = -topInset
inTableView.scrollIndicatorInsets.top = topInset
}
Yeah - a bit annoying.
I have a nib with a single tableview within the main view, not using autolayout. There is a tabbar, navigationbar and a statusbar and the app needs to work back to 5.0. In Interface builder that neat 'see it in iOS7 and iOS6.1 side-by-side' thing works, showing the tables neatly fitting (once the iOS6/7 deltas were set properly).
However running on a device or simulator there was a large gap at the top of the table, which was as a result of a content inset (which pretty much matched by iOS6/7 vertical delta) that was set to zero in the nib.
Only solution I got was in viewWillAppear to put in [_tableView setContentInset:UIEdgeInsetsZero].
Another ugly hack with a pretty on-screen result.....
[self.navigationController setNavigationBarHidden:YES animated:YES];
in:
- (void)viewDidLoad

UICollectionView in UITableViewCell - touches only received in a portion of view

I'm having an issue receiving touches in UICollectionViews contained within UITableViewCells. The desired effect is a UITableView with n rows of horizontally scrolling UICollectionViews. The view is displaying correctly but the collection views only receive touches in the top 44px. I imagine that the table view is still in the process of initialization when the collection views are created and that the collection views are using UITableView's default cell height when setting up their gesture recognizers. Relevant code is below.
In my UITableViewCell subclass, I create a container view for the UICollectionView:
- (void)layoutSubviews
{
if (!_collectionViewContainer) {
_collectionViewContainer = [[CVTCollectionViewContainer alloc] init];
_collectionViewContainer.frame = self.bounds;
[self.contentView addSubview:_collectionViewContainer];
};
}
In my container view, I instantiate a UICollectionView:
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
if (!self.collectionView) {
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
self.collectionView = [[UICollectionView alloc] initWithFrame:self.bounds collectionViewLayout:flowLayout];
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
self.collectionView.backgroundColor = [UIColor clearColor];
flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
flowLayout.itemSize = CGSizeMake(130.0, 130.0);
[_collectionView registerClass:[CVTCollectionViewCell class] forCellWithReuseIdentifier:#"Collection view cell"];
[self addSubview:self.collectionView];
}
}
There's nothing interesting in my UITableViewController, just that I return 200 in tableView:heightForRowAtIndexPath:
It occurred to me that the layoutSubviews method of my UITableViewCell subclass may be the wrong place for initialization of the 'container' view for my UICollectionView. But, when I NSLog(#"cell: %#", self); in layoutSubviews, the cell's frame shows the desired height (200). Still, I have a feeling that I am doing my setup for the collection view too early, but I can't think of where else I might perform this work.
So, the gist: how can I add a UICollectionView to a UITableViewCell and make sure that the UICollectionView's gesture recognizers respond in the entirety of the collection view, rather than just the top 44 px?
Thanks in advance, as always.
Bit of a facepalm here, I was using a UITableViewController that was created in storyboard but mostly configured in code. In storyboard, I had not set the row height of the table view, so it was still at the default 44 px. Of course, the table view looked at IB for its initial config so, although tableView:heightForRowAtIndexPath: was being called and the cells were displaying correctly, the initial height set in IB was affecting how the cell's subviews were created.

UIView loaded with Nib autoresizing issue

I have a UIView subclass -
#interface DatePickerPopup : UIView
UIToolbar *toolbar;
UIDatePicker *datePicker;
#end
#implementation
- (id)initWithFrame:(CGRect)frame
{
NSArray *xib =
[[NSBundle mainBundle]
loadNibNamed:#"DatePickerPopup"
owner:self
options:nil];
self = [xib objectAtIndex:0];
if (self) {
}
return self;
}
#end
and the nib looks like -
In my UIViewController containing the DatePickerPopup (datePopup):
- (void)viewDidLoad
{
datePopup = [[DatePickerPopup alloc] initWithRect:CGRectZero];
CGRect newFrame = datePopup.frame;
newFrame.y = 200.0f; //lets say this aligns it to the bottom in portrait
datePopup.frame = newFrame;
// Normally happens when accessory button pressed but for brevity...
[self.view.superview addSubview:datePopup];
}
- (void)willAnimateRotationToInterfaceOrientation:
(UIInterfaceOrientation)toInterfaceOrientation
duration:(NSTimeInterval)duration
{
CGRect screen = [[UIScreen mainScreen] bounds];
if (toInterfaceOrientation == UIInterfaceOrientationPortrait ||
toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
{
self.datePopup.frame =
CGRectMake(0.0f, newHeightPortrait, screen.size.width, 260.0f);
}
else
{
self.datePopup.frame =
CGRectMake(0.0f, newHeightLandscape, screen.size.width, 260.0f);
}
}
However, this gets stretched out for some reason when the orientation changes the view gets stretched to the height of the screen bounds - the navigation bar...
after viewDidLoad
after willAutorotate...
Since your view controller appears to be managed by a navigation controller, calling [self.view.superview addSubview:datePopup]; adds your popup as a subview of a UIViewControllerWrapperView, which is one of the private classes UIKit uses to implement the functionality of UINavigationController. Messing with UIKit's private view hierarchy is always risky. In this case, based on the behavior you're seeing, it seems likely that UIKit expects any subview of UIViewControllerWrapperView to be a view controller's view, so it resizes your popup accordingly.
I think the safest way to resolve this is to have your view controller's view be a wrapper that contains your tableView and, when necessary, your popup view. Unfortunately using a wrapper view means that the view controller can't be a UITableViewController. You'll have to change the superclass to UIViewController, set a custom tableView property, and manually adopt the UITableViewDataSource and UITableViewDelegate protocols.
Note: You might be tempted to add your popover as a subview of your window, but I'm not recommending that because UIWindow only autorotates its topmost subview corresponding to a view controller. This means that if you add your popover to your window, it won't autorotate.
EDIT: BTW, by reassigning self = [xib objectAtIndex:0]; in initWithFrame:, you're leaking the object that was originally alloc'd. If you're going to reassign self in this way, you should release the existing object first.
Add the
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
method in the viewController class and return YES. If this method returns YES, only then will the device support landscape orientation. Try out this extra code and see if it helps...
You can set the frame size for landscape in this method itself instead of the current method. PS: I just saw you've used a UIView instead of controller...you might want to change to controller.

Programmatically layout iPhone UIView?

I am using the iPhone toolchain on Linux and so I have no Interface Builder. So how could I layout my view in my ViewController subclass? For example, I want a UITextView in the middle of the screen? Should I do this in the loadView or viewDidLoad. Do I also have to set the view for the ViewController subclass to itself?
It is not an easy job to layout all the view using code. Here are some code:
UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake (100, 100, 100, 100)];
[self.view addSubview:textView];
The frame is the place (the first and second argument is x and y coordinator) and the size (the third and fourth argument is width and height of the text view).
Using this way, you can add any view into your class. Some of the view is built in and you don't have to draw yourself, some of them is not, and you need to subclass UIView and override drawRect.
You should do this in viewDidLoad when your main view controller is finished loading
I've written an open source project that does exactly this:
https://github.com/charlesmchen/WeViews
Here's another project that you might find useful:
http://code.google.com/p/layoutmanagers/
I usually build the entire view hierarchy in the loadView method and perform additional setup in the viewDidLoad, for example to set up the subviews content to reflect the data associated to the view controller. The important thing is to set the view controller view outlet in the loadView method.
#synthesize label; // #property(nonatomic,retain) UILabel *label declared in the interface.
-(void)loadView {
// Origin's y is 20 to take the status bar into account, height is 460 for the very same reason.
UIView *aView = [[UIView alloc]initWithFrame:CGRectMake(0,20,320,460)];
[aView setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
[aView setAutoresizeSubviews:YES];
// The 150x50 label will appear in the middle of the view.
UILabel *aLabel = [[UILabel alloc]initWithFrame:CGRectMake((320-150)/2,(460-50)/250,150,50)];
// Label will maintain the distance from the bottom and right margin upon rotation.
[aLabel setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleLeftMargin];
// Add the label to the view hiearchy.
[self setLabel:aLabel];
[aView addSubview:aLabel];
// Set aView outlet to be the outlet for this view controller. This is critical.
[self setView:aView];
// Cleanup.
[aLabel release];
[aView release];
}
-(void)viewDidLoad {
// Additional and conditional setup.
// labelText is an istance variable that hold the current text for the label. This way, if you
// change the label text at runtime, you will be able to restore its value if the view has been
// unloaded because of a memory warning.
NSString *text = [self labelText];
[label setText:text];
}
-(void)viewDidUnload {
// The superclass implementation will release the view outlet.
[super viewDidUnload];
// Set the label to nil.
[self setLabel:nil];
}
The biggest difficulty is probably understanding how IB settings map to UIView variables and methods, for example the autoresizing mask. Apple's UIView and UIViewController class references are full of useful informations.

switch between uiviews with buttons and not uinavigation controllers

I have seen the post for How to switch views by buttons on iPhone? but this doesn't answer how to switch back and forth between views with buttons. The person that asked the question settled on the answer that they could switch between views with uinavigationcontroller.
I put the following code in an ibaction that kicks off when a button is pressed in the primary view.
PhoneNumberViewController *phoneNumberViewController1 = [[PhoneNumberViewController alloc] initWithNibName:#"PhoneNumberView2" bundle:nil];
self.phoneNumberViewController = phoneNumberViewController1;
[self.view removeFromSuperview];
[self.view insertSubview: phoneNumberViewController1.view atIndex:0];
When this code executes the whole view just goes blank. If I omit the removefromsuperview portion then the view disappears behind my button but the button still remains. I'm not sure if this is the right way to switch between buttons but if anyone knows how to do this please help. Also if anyone knows about any example projects that switch between views with buttons let me know.
Thanks a million!
You removed the view controller's view from it's superview and then added a subview to it. The view hierarchy is broken at the view controller's superview (likely your window). That is why you are getting a blank screen.
You'd likely want to keep a reference around to the original view and then swap it out to the new view by setting the view controller's view to the new view.
// origView is an instance variable/IBOutlet to your original view.
- (IBAction)switchToPhoneView:(id)sender {
if (origView == nil)
origView = self.view;
self.view = phoneViewController.view;
}
- (IBAction)switchToOriginalView:(id)sender {
self.view = origView;
}
The technique I usually use involves creating a superview class which contains a toolbar at the bottom, and a content view UIView class filling the rest of the screen.
I then add subviews to the content view to change the views based on button clicks. This approach makes it so the toolbar on the same is constant across all views. I start by defining a helper function like this:
-(void) clearContentView {
//helper to clear content view
for (UIView *view in [self.contentView subviews]){
[view removeFromSuperview];
}
}
My IBAction then looks like this:
-(IBAction) buttonClicked{
self.title = #"Images"; //change title of view
[self clearContentView]; //clear content view
[self.contentView addSubview:self.imagesViewController.view]; //add new view
[self.imagesViewController viewWillAppear:YES]; //make sure new view is updated
[self enableButtons]; //enable all other buttons on toolbar
self.imagesButton.enabled = NO; //disable currently selected button
}