I'm trying to add searching to my UITableViewController via UISearchDisplayController, however I'm seeing a very weird animation glitch when searching begins and ends.
On iPhone, the animation into the navigation bar works fine. However, when ending search, the navigation bar lags behind the search bar while animating down. This causes a white strip to show between the navigation bar and the search bar. On iPad, the animations are completely messed up.
iPhone video
and
iPad video
As the above videos show, the stock apps do not suffer from these animation glitches. Does anybody have any ideas what is causing the issues?
I'm creating the UISearchDisplayController with the following inside the "Master" view controller.
- (void)viewDidLoad
{
[super viewDidLoad];
UISearchBar* searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 44.0)];
searchBar.delegate = self;
self.tableView.tableHeaderView = searchBar;
self.searchController = [[UISearchDisplayController alloc] initWithSearchBar:searchBar contentsController:self];
self.searchController.delegate = self;
self.searchController.searchResultsDataSource = self;
self.searchController.searchResultsDelegate = self;
}
I also tried doing this using Storyboards but the same animation glitches occur.
Solution for non-translucent navbar (less code, less universality)
The white glitch you see is a tableview's background color which is visible through a gap between navbar and searchbar. The gap is definitely Apple developer's oversight.
So the solution looks like this:
- (void) searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller
{
UIView *topTableViewBG = [[UIView alloc] initWithFrame:CGRectMake(0, -64, CGRectGetWidth(self.tableView.bounds), 64)];
topTableViewBG.backgroundColor = self.navigationController.navigationBar.backgroundColor;
topTableViewBG.tag = 1234567;
[self.tableView insertSubview:topTableViewBG belowSubview:self.tableView.tableHeaderView];
}
- (void)searchDisplayControllerDidEndSearch:(UISearchDisplayController *)controller
{
[[self.tableView viewWithTag:1234567] removeFromSuperview];
}
here I add a custom view right under the search bar. It happens just before transition to normal VC. The view is positioned at the special point so it covers the gap between the navbar and the searchbar.
After the transition is finished, I remove the custom view.
UPDATE: Universal solution (more code, more universality)
the solution above is good only if there is non-translucent navbar. For translucent navbar there is a challange to find correct color for our 'gap-stopper' view. But as soon we are pretty free to change searchbar color, we can use searchbar color for gap-stopper.
Lets make some changes to our code
First, we need non-translucent searchbar, so set background image to search bar:
UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
[searchBar setBackgroundImage:[UIImage pixelImageWithColor:SEARCHBAR_GRAY_COLOR] forBarPosition:UIBarPositionAny barMetrics:UIBarMetricsDefault];
searchBar.delegate = self;
self.tableView.tableHeaderView = searchBar;
here is the UIImage category method, that was used above:
+ (UIImage *)pixelImageWithColor:(UIColor *)color {
CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB ();
CGContextRef ctx = CGBitmapContextCreate (NULL, 1, 1, 8, 0, cs, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrderDefault);
CGColorSpaceRelease (cs);
CGContextSetFillColorWithColor (ctx, color.CGColor);
CGContextFillRect (ctx, CGRectMake (0.0f, 0.0f, 1.0f, 1.0f));
CGImageRef cgImage = CGBitmapContextCreateImage (ctx);
CGContextRelease (ctx);
UIImage *result = [UIImage imageWithCGImage:cgImage];
CGImageRelease (cgImage);
return result;
}
Then change our searchDisplayControllerWillEndSearch method:
- (void) searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller
{
UIView *topTableViewBG = [[UIView alloc] initWithFrame:CGRectMake(0, -64, CGRectGetWidth(self.tableView.bounds), 64)];
topTableViewBG.backgroundColor = SEARCHBAR_GRAY_COLOR;
topTableViewBG.tag = 1234567;
[self.tableView insertSubview:topTableViewBG belowSubview:self.tableView.tableHeaderView];
}
and finally the searchDisplayControllerDidEndSearch method remains unchanged:
- (void)searchDisplayControllerDidEndSearch:(UISearchDisplayController *)controller
{
[[self.tableView viewWithTag:1234567] removeFromSuperview];
}
Honestly this solution is more universal and it looks much prettier than one I've described in first part of the answer.
I find the best solution for me, it's looks exactly as I need, I'm using swift but if you need you can easily translate it to objective-c
self.edgesForExtendedLayout = .None
let barBacgroundView = UIView(frame: CGRectMake(0, 0, self.view.frame.size.width, 64))
barBacgroundView.backgroundColor = self.tableView.backgroundColor
UIApplication.sharedApplication().delegate!.window!!.insertSubview(barBacgroundView, atIndex: 0)
Insert this code in viewDidLoad() and that's it. Hope this helps :)
Related
I'm trying to use a custom overlay on the UIImagePickerController and also be able to move and scale the resulting image. The problem is when I set showsCameraControls=YES, the overlay won't show but I can use the the move and scale functionality. However, when showsCameraControls=NO, the overlay appears, but when I take the picture, I don't even get the option to crop the image. The actual view loaded from the xib is just a UIToolbar with two buttons, one of which takes the picture
I have a controller (OverlayViewController) which loads the overlay from a xib file as it's view. This controller also has a UIImagePickerController so it sets the overlay of the UIImagePickerController to the view of the OverlayController. This first method is called in a different controller to set it all up and display it. In summary, I need to be able see the overlayView and use it's button to take the picture, which I can then move and scale
-(void) openCameraForImage
{
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
{
self.overlayViewController =
[[OverlayViewController alloc] initWithNibName:#"OverlayViewController" bundle:nil];
// as a delegate we will be notified when pictures are taken and when to dismiss the image picker
self.overlayViewController.delegate = self;
[self.overlayViewController setupImagePicker:UIImagePickerControllerSourceTypeCamera];
[self presentModalViewController:self.overlayViewController.imagePickerController animated:YES];
}
else {
[Common ShowAlert:#"Alert" andMessage:#"There is no camera on this device"];
}
}
This method is in OverlayViewController and does the work of setting up the Overlay.
- (void)setupImagePicker:(UIImagePickerControllerSourceType)sourceType
{
self.imagePickerController.sourceType = sourceType;
[self.imagePickerController setAllowsEditing:YES];
if (sourceType == UIImagePickerControllerSourceTypeCamera)
{
// user wants to use the camera interface
//
self.imagePickerController.showsCameraControls = NO; //This line allows overlay to show but move and scale doesn't work
self.imagePickerController.allowsEditing = YES;
//[self.view setUserInteractionEnabled:NO];
if ([[self.imagePickerController.cameraOverlayView subviews] count] == 0)
{
// setup our custom overlay view for the camera
//
// ensure that our custom view's frame fits within the parent frame
CGRect overlayViewFrame = self.imagePickerController.cameraOverlayView.frame;
CGRect newFrame = CGRectMake(0.0,
CGRectGetHeight(overlayViewFrame) -
self.view.frame.size.height - 10.0,
CGRectGetWidth(overlayViewFrame),
self.view.frame.size.height + 10.0);
self.view.frame = newFrame;
self.imagePickerController.cameraOverlayView = self.view;
}
}
}
This method in OverlayViewConroller takes the picture
- (IBAction)takePhoto:(id)sender
{
[self.imagePickerController takePicture];
}
I think you can put your image picker code in openCameraForImage
In your OverlayViewController, just use it to purely show the Overlay for your camera.
#implementation RiskCameraOverlayViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.view setBackgroundColor:[UIColor blackColor]];
opUpImage = [UIImage imageNamed:#"camera_overlay_op_up"];
opDownImage = [UIImage imageNamed:#"camera_overlay_op_down"];
opUpFrameBegin = CGRectMake(0, -50, 320, 358);
opUpImageView = [[UIImageView alloc] initWithFrame:opUpFrameBegin];
[opUpImageView setImage:opUpImage];
opDownFrameBegin = CGRectMake(0, 265, 320, 314);
opDownImageView = [[UIImageView alloc] initWithFrame:opDownFrameBegin];
[opDownImageView setImage:opDownImage];
[self.view addSubview:opUpImageView];
[self.view addSubview:opDownImageView];
}
You might reach an issue where "Use photo" or "Retake" is not working, do a
[self.view removeFromSuperview];
I would like to display an image (width: 320 pixels, height: 1250 pixels) in an image view.
When I compile the application I get no scrolling at all. The view stays fixed.
What I did:
Added an UIScrollView via Interface Builder to my view.
Added an UIImageView via Interface Builder to my view.
Verified that UIImageView is below UIScrollView in Interface Builder.
Set the size of the UIScrollView with ViewDidLoad.
How do I do this?
Code:
- (void)viewDidLoad
{
[super viewDidLoad];
scrollView.contentSize = CGSizeMake(320, 1250);
}
Screenshots:
ImageView:
ScrollView:
I just have done the same task..
Try this one.....
scrollView.delegate = self;
scrollView.scrollEnabled = YES;
int scrollWidth = 120;
scrollView.contentSize = CGSizeMake(scrollWidth,80);
int xOffset = 0;
imageView.image = [UIImage imageNamed:[imagesName objectAtIndex:0]];
for(int index=0; index < [imagesName count]; index++)
{
UIImageView *img = [[UIImageView alloc] init];
img.bounds = CGRectMake(10, 10, 50, 50);
img.frame = CGRectMake(5+xOffset, 0, 50, 50);
NSLog(#"image: %#",[imagesName objectAtIndex:index]);
img.image = [UIImage imageNamed:[imagesName objectAtIndex:index]];
[images insertObject:img atIndex:index];
scrollView.contentSize = CGSizeMake(scrollWidth+xOffset,110);
[scrollView addSubview:[images objectAtIndex:index]];
xOffset += 70;
}
Also set this one....
imagesName = [[NSArray alloc]initWithObjects:#"image1.jpg",#"image2.jpg",#"image3.jpg",#"image4.jpg",#"image5.jpg",#"image6.png",#"image7.png",#"image9.png",nil];
images = [[NSMutableArray alloc]init];
So for me the problem was that setting the content size didn't work in viewDidLoad(). I tried everything and I didn't understand why it wouldn't want to work, and then I tried the same stuff in viewDidAppear() and it magically worked...
From you last screenshot and from your comments it looks like your scrollView is way to big.
The scrollview must be visible on screen completely. For example a full screen UIScrollView on iPhone would have a size of 320 x 460.
If the scrollview is the same size as its content you can't scroll.
The greenish rectangle shows the size of your scrollview, the pinkish the size of your content (your image):
Xcode 11+, Swift 5
You can find the complete solution here.
I came across this same issue on iOS6 and the solution was to programmatically adjust the ContentSize.
So I will just quote from Raja (above) to show this:
CGSize scrollViewContentSize = CGSizeMake(320, 400);
[self.scrollView setContentSize:scrollViewContentSize];
NOTE: I was not having this issue on iOS5.. seems like iOS6 decided to do alot of prank just like the rotation/orientation saga
Since Xcode 5 it does not work like before. Also scrolling to the end of a scrollable text field makes problems. There are also differences between doing it on iPhone or iPad. On iPhone it worked without delayed timer.
This worked for me:
- (void)viewDidLoad
{
[super viewDidLoad];
NSTimer *timerforScrollView;
timerforScrollView =[NSTimer scheduledTimerWithTimeInterval:0.1
target:self selector:#selector(forScrollView)userInfo:nil repeats:NO];
}
- (void) forScrollView {
[scrollviewPad setScrollEnabled:YES];
[scrollviewPad setContentSize:CGSizeMake(768, 1015)]; // must be greater then the size in Storyboard
}
I found I had a similar problem but none of the above code worked. The issue was due to autolayout. I found that if I turned off autolayout by going to the storyboard clicking on Utilities -> File Inspector and unchecked Use Autolayout the scrolling did work (I also set scroll.contentSize = ...).
Sometimes autoLayout checkbox is ticked in the xib. That also creates this issue in xcode 5. Which makes the UIScrollView scrolling off.
Don't forget to add the category protocol to the interface, like this
#interface MyViewController : <UIScrollViewDelegate>
If you don't, you will not be able to set the scroll view delegate to self
(i.e. [MyScrollView setDelegate:self];)
If you do this, it should work.
My code is:
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[contentScrollView setDelegate:self];
[contentScrollView setScrollEnabled:YES];
contentScrollView.contentSize = CGSizeMake(310, 500);
contentScrollView.frame = CGRectMake(5, 188, 310, 193);
}
Did you assign the scroll's view delegate? Always remember these:
[self.scrollView setDelegate:self];
[self.scrollView setScrollEnabled:YES];
The image view has to be a subview (so inside AND below) of the scrollview. From your description it seems they are paralell
You forgot one line. Add this to your view load function:
[scrollView setScrollEnabled:YES];
You could try disabling AutoLayout. In XCode 5 I tested all the above answers and I could only scroll it by disabling autolayout and activating all autosizing masks under the Size Inspector. The following code was used too:
self.scrollView.contentSize = CGSizeMake(320, 900);
self.scrollView.delegate = self;
self.scrollView.scrollEnabled = YES;
self.scrollView.frame = self.view.frame;
CGRect scrollViewFrame = CGRectMake(0, 0, 320, 400);
self.scrollView = [[UIScrollView alloc] initWithFrame:scrollViewFrame];
[self.view addSubview:self.scrollView];
CGSize scrollViewContentSize = CGSizeMake(320, 400);
[self.scrollView setContentSize:scrollViewContentSize];
scrollView.delegate = self;
[self.scrollView setBackgroundColor:[UIColor blackColor]];
[scrollView setCanCancelContentTouches:NO];
scrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
scrollView.clipsToBounds = YES;
scrollView.scrollEnabled = YES;
scrollView.pagingEnabled = YES;
NSUInteger nimages = 0;
CGFloat cx = 0;
for (; ; nimages++) {
NSString *imageName = [NSString stringWithFormat:#"image%d.jpg", (nimages + 1)];
UIImage *image = [UIImage imageNamed:imageName];
if (image == nil) {
break;
}
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
CGRect rect = imageView.frame;
rect.size.height = image.size.height;
rect.size.width = image.size.width;
rect.origin.x = ((scrollView.frame.size.width - image.size.width) / 2) + cx;
rect.origin.y = ((scrollView.frame.size.height - image.size.height) / 2);
imageView.frame = rect;
[scrollView addSubview:imageView];
[imageView release];
cx += scrollView.frame.size.width;
}
[scrollView setContentSize:CGSizeMake(cx, [scrollView bounds].size.height)];
Assuming that scrollView is a subview of view and fills it entirely you can use the following in viewDidLoad:
[scrollView setContentSize: CGSizeMake(self.view.frame.size.width, self.view.frame.size.height)];
I had a UIScrollView that was not scrolling and this allowed it to scroll.
Just add code
scrollView.contentSize = CGSizeMake(WIDTH,HEIGHT);
to method -(void)viewDidLayoutSubviews.
For more information checkout Stanford CS193p Lecture No 8 to understand View Controller Life cycle.
I had the same issue and was looking for the answer in this thread. I tried all the stuff, but nothing works. Then I found this:
.
You just need to deselect "Use Auto Layout" in File Inspector of your ViewController. Ta-Da, it works immediately for me. Enjoy.
I have applied following code to my application to change the navigation bar image.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.navigationController.navigationBar setTintColor:[UIColor blackColor]];
[self setNavigationBarTitle];
}
-(void)setNavigationBarTitle {
UIView *aViewForTitle=[[[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 45)] autorelease];
UIImageView *aImg=[[UIImageView alloc] initWithFrame:CGRectMake(-8, 0, 320, 45)];
aImg.image=[UIImage imageNamed:#"MyTabBG.png"];
[aViewForTitle addSubview:aImg]; [aImg release];
UILabel *lbl=[[[UILabel alloc] initWithFrame:CGRectMake(0, 0, 305, 45)] autorelease];
lbl.backgroundColor=[UIColor clearColor]; lbl.font=[UIFont fontWithName:#"Trebuchet MS" size:22];
lbl.shadowColor=[UIColor blackColor]; [lbl setShadowOffset:CGSizeMake(1,1)];
lbl.textAlignment=UITextAlignmentCenter; lbl.textColor=[UIColor whiteColor]; lbl.text=#"Mobile Tennis Coach Overview";
[aViewForTitle addSubview:lbl];
[self.navigationItem.titleView addSubview:aViewForTitle];
}
See following images. You can see the problem that I am facing.
Each view controller of my application has above methods to set the navigation bar background.
How ever, when I push a new view controller to my application. Back button will be appear.
I need back button to be appear. But The image should be behind back button.
Now I am little confused here.
Can you help me regarding this?
Thanks in advance for sharing your knowledge with me.
Thanks a lot.
After an annoying night, I found a slight tweak to this, if you use drawLayer. With drawRect, when you play a video, or youtube video, the navbar is replaced with the image. And I read a few posts that this caused their app to be rejected.
#implementation UINavigationBar (UINavigationBarCategory)
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
if([self isMemberOfClass:[UINavigationBar class]])
{
UIImage *image = [UIImage imageNamed:#"navBarBackground.png"];
CGContextClip(ctx);
CGContextTranslateCTM(ctx, 0, image.size.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
CGContextDrawImage(ctx,
CGRectMake(0, 0, self.frame.size.width, self.frame.size.height), image.CGImage);
}
else
{
[super drawLayer:layer inContext:ctx];
}
}
#end
If this article is accurate, all should be fine with this approach:
http://developer.apple.com/iphone/library/qa/qa2009/qa1637.html
The short answer is that modifying the structure of the UINavigationBar is not supported by Apple. They really do not want you do be doing what you are trying to do. That is what is causing the issue you are seeing.
Please file a radar requesting this feature so that it can get enough attention to be officially added at some point.
Having said that, to solve the issue you can add a category to UINavigationBar with a -drawRect: method and draw the background image in that method. Something like this will work:
- (void)drawRect:(CGRect)rect
{
static UIImage *image;
if (!image) {
image = [UIImage imageNamed: #"HeaderBackground.png"];
if (!image) image = [UIImage imageNamed:#"DefaultHeader.png"];
}
if (!image) return;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, CGRectMake(0, 0, self.frame.size.width, self.frame.size.height), image.CGImage);
}
I have a UIViewController subclass to control a UIView that I want to add to a UIScrollView. I only want the view to be 100px high, but when I add it to the scroll view it gets made 460px high, ignoring the frame size I set:
MyViewController *vc = [[MyViewController alloc] init];
vc.view.frame = CGRectMake(0, 0, 320, 100);
myScrollView.autoresizesSubviews = NO
[myScrollView addSubview:vc.view];
[vc release];
I have set the scroll view to not autoresize subviews but it seems this is still happening! What can I do?
I have also tried setting the frame size inside loadView: in the UIViewController (which is where I will add all my controls and will need access to the size of the view) but that doesnt work either!
- (void)loadView {
[super loadView];
self.view.backgroundColor = [UIColor redColor];
self.view.frame = CGRectMake(0, 0, 320, 100); // still doesnt work
}
Any help would be greatly appreciated!
You are using loadView incorrectly, im even suprised you see a view (you shouldnt since in load view you arent assigns the vc view to anything), in loadView you must assign your view to a new UIView i nstance, anyway, you should be doing the same but in viewDidLoad instead of load view, that might work for you
Here is a snippet of how I do it. Note that the origin is with respect to the view you are adding to (in my case 'self').
appRect.origin=CGPointMake(0.0, 0.0);// origin
appRect.size = CGSizeMake(320.0f, 100.0f); //size
CGRect frame = CGRectInset(appRect, 0.0f, 0.0f);
gv=[[GraphicsView alloc] initWithFrame:appRect object:[model me]];
[gv setFrame:frame];
[self.view addSubview:gv];
[gv release];
I'm programatically adding two custom UIPickerViews to another view (MainView). They work just fine but they are not visible until a touch event occurs in any part of the MainView. I've checked the class references for UIPickerView and UIView but haven't found anything that manages to "refresh" the view, unless I'm missing something obvious?
Here's my drawRect method in MainView.m. I've tried doing the same thing in viewDidLoad but with no success. Could the custom rotations/transforms etc have something to do with it?
- (void)drawRect:(CGRect)rect {
CGRect pickerFrame = CGRectMake(50, -32, 30, 180);
m_picker1 = [[UIPickerView alloc] initWithFrame:pickerFrame];
m_picker1.delegate = self;
m_picker1.tag = k_ptag1;
m_picker1.showsSelectionIndicator =YES;
m_picker1.backgroundColor = [UIColor clearColor];
CGAffineTransform rotate = CGAffineTransformMakeRotation(3.14/2);
rotate = CGAffineTransformScale(rotate, 0.075, 0.85);
[m_picker1 setTransform:rotate];
[self addSubview:m_picker1];
pickerFrame = CGRectMake(50, 67, 30, 180);
m_picker2 = [[UIPickerView alloc] initWithFrame:pickerFrame];
m_picker2.delegate = self;
m_picker2.tag = k_ptag2;
m_picker2.showsSelectionIndicator =YES;
m_picker2.backgroundColor = [UIColor clearColor];
rotate = CGAffineTransformMakeRotation(3.14/2);
rotate = CGAffineTransformScale(rotate, 0.075, 0.85);
[m_picker2 setTransform:rotate];
[self addSubview:m_picker2];
}
You add subviews in a view's controller, not the view itself. I would suggest familiarising yourself with the MVC design pattern.
drawRect is only supposed to be used for the actual drawing of the view itself, not subviews.