Move a UIView object when ViewController is rotated - iphone

I have a custom UIView that I'm using for the UITableView header. I'm creating it in code. (I'm basically following the TVAnimationGestures from WWDC 2010). For UI elements that are some offset from the left hand side, it looks good in either orientation. However, if I want a UILabel that is offset from the right hand size, I'm not sure what to do. If I were in IB, I would use the springs/struts. But not sure how to do that in code. Thanks.

springs/struts in IB is a GUI interface to autoresizingMask:
http://developer.apple.com/library/ios/#documentation/uikit/reference/uiview_class/UIView/UIView.html#//apple_ref/doc/uid/TP40006816-CH3-SW6
For right justification, You want to position the label with the correct offset when you first create it and set autoresizingMask to UIViewAutoresizingFlexibleLeftMargin (and any other that you want.)

A simple way to do this is to implement a method in the view which will place it's subviews according to the UIInterfaceOrientation.
You can then call this method from the UIViewController when the rotation is detected.
For example:
In YourView.h
#interface YourView : UIView {
}
// Public API
- (void)placeWidgetsForInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation;
#end
In YourView.m
#import "YourView.h"
#implementation YourView
...
#pragma mark -
#pragma mark Public API
- (void)placeWidgetsForInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Change frames of subviews according to orientation in here
}
#end
Then in YourController.m
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
YourView *theView = (YourView *) self.view; // Or where ever your view is
[theView placeWidgetsForInterfaceOrientation:toInterfaceOrientation];
}

Related

check if UIWebView scrolls to the bottom

How do I check/call a method when a user is at the bottom of the UIWebView when scrolling? I want to popup a view (add a subiview) when the user is at the bottom of the content.
Building on other ideas here, UIWebView conforms to the UIScrollViewDelegate protocol. You could subclass UIWebView and override the appropriate UIScrollViewDelegate methods, calling [super ...] so the original behavior is still present.
#interface SpecialWebview : UIWebView
#end
#implementation SpecialWebview
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[super scrollViewDidScroll:scrollView];
// Check scroll position and handle events as needed...
}
#end
First get the reference to the UIWebView scrollView using this property:
#property(nonatomic, readonly, retain) UIScrollView *scrollView
Once you get the reference to it you could check for the contentSize of it and compare it with the contentOffSet. You cannot make yourself a delegate of it, since the UIWebView is already the delegate for the scrollView, but that trick should work.
The problem is that you don't get the callBack when the scrolling is happening... so you would have to check every so often. Otherwise you will have to make yourself the delegate of the scrollView, and implement all the methods that UIWebView is implementing and mimic that behavior, and then check on the didScroll for that condition.
Another option would be to inject some JavaScript into the web view and then use the window.onscroll event in JavaScript to detect when the user has scrolled to the bottom of the window (you can find plenty of examples online if detecting scroll events using JS).
Once you detect the the user is at the bottom, you could call back to the app by loading a fake url in the web view from the JavaScript by saying document.location.href = "http://madeupcallbackurl", then use the web view delegate to intercept that request and perform your native code logic.
Using the scrollview delegate is probably easier though if you can make that work!
Sorry, I was thinking UITableView. You are asking about a UIWebView. I don't know if this will work with the UIWebView.
I have code that does this using a UIScrollViewDelegate.
#interface myViewController : UIViewController_iPad <UIScrollViewDelegate> {
UITableView *myTableView;
...
}
- (void)viewDidLoad {
myTableView.delegate = self;
...
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// If necessary, verify this scrollview is your table:
// if (scrollView == myTableView) {
CGPoint offset = myTableView.contentOffset;
if (myTableView.contentSize.height - offset.y >= myTableView.bounds.size.height)
{
// The table scrolled to the bottom. Do your stuff...
}
// }
}
This is how I would do it.
UIWebView conforms to UIScrollViewDelegate. Use the - (void)scrollViewDidScroll:(UIScrollView *)scrollView callback to keep track of when your UIWebView is scrolling:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
//The webview is is scrolling
int xPosition = [[webView stringByEvaluatingJavaScriptFromString: #"scrollX"] intValue];
int yPosition = [[webView stringByEvaluatingJavaScriptFromString: #"scrollY"] intValue];
}
Now this method will be invoked whenever your webView scrolls. Your webView will have scrolled to the bottom when:
webView_content_height - webView(y) = webView.frame.height; //pseudo code
Your delegate callback will now look like:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
//The webview is is scrolling
int xPosition = [[webView stringByEvaluatingJavaScriptFromString: #"scrollX"] intValue];
int yPosition = [[webView stringByEvaluatingJavaScriptFromString: #"scrollY"] intValue];
if([webView stringByEvaluatingJavaScriptFromString: #"document.body.offsetHeight"] - yPosition == webView.frame.height)
{
//The user scrolled to the bottom of the webview
}
}
What the code translates to is that when the user scrolls to the bottom of the content, the difference between the webView's y Position (which is the y-coordinate of the webView's top left corner) and the bottom of the content will be equal to the height of the webview frame. The code acts upon this condition being satisfied to enter inside the if condition.
To be on the safe side, you could modify the if condition to have a margin for error (maybe + or - 10 pixels).

how to fix the margin adjustment code attached for a 'Grouped" UITableView?

Trying to achieve the look & feel of a Grouped TableView, with only one item per section, and but with hardly any margins (gives me rounded edged view with the ability for the user to be able to choose the color they want).
I have it working, HOWEVER when the user changes orientation I had to use the didRotateFromInterfaceOrientation method (as willRotateToInterfaceOrientation didn't work), BUT the effect is that you do see the margins change quickly in that fraction of a second after the tableView displays.
QUESTION - Any way to fix things so one doesn't see this transition?
- (void) removeMargins {
CGFloat marginAdjustment = 7.0;
CGRect f = CGRectMake(-marginAdjustment, 0, self.tableView.frame.size.width + (2 * marginAdjustment), self.tableView.frame.size.height);
self.tableView.frame = f;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self removeMargins];
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
[self removeMargins];
}
I Think the problem is that in willRotateToInterfaceOrientation the frame of the tableView hasn't resized yet, so your frame calculations are incorrect. on didRotateFromInterfaceOrientation the frame has already changed.
I think the easiest way to solve this is to subclass UITableView and override layoutSubviews. This method is called every time the frame of the view changes in a way that may require it's subviews to change.
The following code worked for me without an animation glitch:
#interface MyTableView : UITableView {
}
#end
#implementation MyTableView
-(void) layoutSubviews
{
[super layoutSubviews];
CGFloat marginAdjustment = 7.0;
if (self.frame.origin.x != -marginAdjustment) // Setting the frame property without this check will call layoutSubviews again and cause a loop
{
CGRect f = CGRectMake(-marginAdjustment, 0, self.frame.size.width + (2 * marginAdjustment), self.frame.size.height);
self.frame = f;
}
}
#end
You say that willRotateToInterfaceOrientation didn't work, but you didn't say why.
If the reason is that willRotateToInterfaceOrientation isn't being called, then please take a look at this question.
If you get this working then I believe your other question will solve itself.

Draw a line over UIViewController

I have my app on iPhone with a UIViewController with some stuff inside.. image, textbox, etc...
is there a way to draw a line or something like this using opengl directly inside the UIViewController?
thanks in advance
Are you sure about the OpenGL?
I believe it will be impossible to use OpenGL above regular views.
You might add a custom view (inherit from UIView) above all the other views and draw anything you want on that view.
This view should have a transparent background color.
In addition, if you want to interact with the other views (tap buttons, edit text views, scroll etc.) then you will have to implement the hitTest method in your custom view in order to pass the touch event to the views that are located under this one...
EDIT: Don't mess with the hitTest. Just uncheck the User Interaction Enabled in the XIB...
EDIT: Code sample:
#interface TransparentDrawingView : UIView {
CGPoint fromPoint;
CGPoint toPoint;
}
- (void)drawLineFrom:(CGPoint)from to:(CGPoint)to;
#end
#implementation TransparentDrawingView
- (void)initObject {
// Initialization code
[super setBackgroundColor:[UIColor clearColor]];
}
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Initialization code
[self initObject];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aCoder {
if (self = [super initWithCoder:aCoder]) {
// Initialization code
[self initObject];
}
return self;
}
- (void)drawRect:(CGRect)rect {
// Drawing code
// Draw a line from 'fromPoint' to 'toPoint'
}
- (void)drawLineFrom:(CGPoint)from to:(CGPoint)to {
fromPoint = from;
toPoint = to;
// Refresh
[self setNeedsDisplay];
}
#end
First I think you should know responsibilities of UIView and UIViewController.
UIView is mainly responsible for drawing (override touchBegin, ToucheMove, etc.), animating, manage subviews, and handle event; UIViewController is mainly responsible for loading, unloading views etc.
So, you should 'draw' this line at your customized view (UIView), not view controller.
Second: If you only need to display some simple shapes or lines. I suggest you use UI controls and images. Even 'drawRect' is not recommended since it will cause more resources used. Surely OpenGL need much resources.
The answer is quite simple, you have to create a new class extending UIView, and override the drawRect function in order to draw (with Quartz...) your line. Use this method if you want gradient background as well.
I am also developping with Adobe Flex 4, and I notice that iPhone SDK has no Skin and Layout "patterns" like Flex 4 does (for instance, UIView could have a Skin (in a separate file..) and a Layout (horizontal, Vertical, Tile....)), for me that is a terrible lack!
Its something that the open source library Three20 tries to bring to the iPhone SDK.

drawRect not being called in my subclass of UIImageView

I have subclassed UIImageView and tried to override drawRect so I could draw on top of the image using Quartz 2D. I know this is a dumb newbie question, but I'm not seeing what I did wrong. Here's the interface:
#import <UIKit/UIKit.h>
#interface UIImageViewCustom : UIImageView {
}
- (void)drawRect:(CGRect)rect;
#end
And the implementation:
#import "UIImageViewCustom.h"
#implementation UIImageViewCustom
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
}
return self;
}
- (void)drawRect:(CGRect)rect {
// do stuff
}
- (void)dealloc {
[super dealloc];
}
#end
I set a breakpoint on drawRect and it never hits, leading me to think it never gets called at all. Isn't it supposed to be called when the view first loads? Have I incorrectly overridden it?
It'll only get called if the view is visible, and dirty. Maybe the problem is in the code that creates the view, or in your Nib, if that's how you're creating it?
You'll also sometimes see breakpoints failing to get set properly if you're trying to debug a "Release" build.
I somehow missed the first time that you're subclassing UIImageView. From the docs:
Special Considerations
The UIImageView class is optimized to
draw its images to the display.
UIImageView will not call drawRect: in a
subclass. If your subclass needs
custom drawing code, it is recommended
you use UIView as the base class.
So there you have it. Given how easy it is to draw an image into your view using [UIImage drawInRect:], or by using CALayer, there's probably no good reason to subclass UIImageView anyway.
Try to add
Edit:
- (id)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
[self setClearsContextBeforeDrawing:YES];//add this line also
}
return self;
}
- (void)setNeedsDisplay{
[self setNeedsDisplayInRect:self.frame];
}
into your code.
hope this helps.
Thanks,
madhup
Not directly answering your question, but may solve your problem:
Why do this with a subclass of UIImageView? Subclassing can always be problematic, especially for classes that aren't designed to be subclassed--and I bet UIImageView isn't. If you just want to draw stuff on top of an image, then create a view with a transparent background, draw whatever you want, and place it directly over the image.
Depending on your specific case, it could make sense to use a UIButton with a background image instead of a UIImageView. You could even set userInteractionEnabled to NO and the result would be indistinguishable from a UIImageView.
In this case your UIButton subclass would simply include:
- (void)drawRect:(CGRect)rect
{
if (kSomethingSpecial) {
[self setBackgroundImage:[UIImage imageNamed:#"RedBackground.png"]
forState:UIControlStateNormal];
}
}
Disclaimer - I would only recommend this if you have plans to use the image as a button at least some of the time. I can't wholeheartedly endorse using a UIButton as a workaround for this drawRect behaviour in UIImageView, as I'm sure Apple has its reasons for writing their API this way, like Mark referenced.

How to stop UITextView from scrolling up when entering it

I have a UITextView included in a UITableViewCell. The layout is correct when the views are initially displayed, but once I click in the UITextView it automatically scrolls up a bit and the top half of the characters on the first line becomes invisible.
This image is when the UITextView is not active:
UITextView not active http://gerodt.homeip.net/uitextview-notactive.png
And this one is when I clicked in the UITextView to make it active:
UITextView active http://gerodt.homeip.net/uitextview-active.png
I do not the UITextView to scroll up at all, it should simple stay fixed. How can I achieve this? I already tried several settings in Interface Builder, but no luck so far.
Any suggestions are appreciated.
Gero
UITextView is a subclass of UIScrollView, so it has a configurable contentInset property. Unfortunately, if you try to change contentInset on a UITextView instance, the bottom edge inset always gets reset to 32. I've run into this before with short UITextView frames and found this to be an issue. I suspect this is what is causing your problem, but you should check the contentInset of your textview in the debugger to be sure.
The workaround/solution is simple: subclass UITextView and override the contentInset method so that it always returns UIEdgeInsetZero. Try this:
//
// BCTextView
//
// UITextView seems to automatically be resetting the contentInset
// bottom margin to 32.0f, causing strange scroll behavior in our small
// textView. Maybe there is a setting for this, but it seems like odd behavior.
// override contentInset to always be zero.
//
#interface BCZeroEdgeTextView : UITextView
#end
#implementation BCZeroEdgeTextView
- (UIEdgeInsets) contentInset { return UIEdgeInsetsZero; }
#end
This is how UITextView behaves according to Apple's engineer this is intended and UITextView is meant for text that are at least a few lines in height. There is no work around to this, use a UITextField instead or increase your UITextView to at least 3 lines in height.
You can also just do:
textView.contentInset=UIEdgeInsetsZero;
in your delegate file.
UITextView is a subclass of UIScrollView, so the answer involves the contentOffset property, which is what is being changed, not the insets or the content size. If the scroll position is correct when the view first appears, then you can store the content offset for later recall.
YourViewController.h snipped
#interface YourViewController : UIViewController <UITextViewDelegate, UIScrollViewDelegate>
#property(nonatomic, weak) IBOutlet UITextView *textView;
#end
YourViewController.m snippet
#implementation YourViewController {
#private
BOOL _freezeScrolling;
CGFloat _lastContentOffsetY;
}
// UITextViewDelegate
- (void)textViewDidBeginEditing:(UITextView *)textView {
// tell the view to hold the scrolling
_freezeScrolling = YES;
_lastContentOffsetY = self.textView.contentOffset.y;
}
// UITextViewDelegate
- (void)textViewDidEndEditing:(UITextView *)textView {
_freezeScrolling = NO;
}
// UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (_freezeScrolling) {
// prevent the scroll view from actually scrolling when we don't want it to
[self repositionScrollView:scrollView newOffset:CGPointMake(scrollView.contentOffset.x, _lastContentOffsetY)];
}
}
// UIScrollViewDelegate
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
// scroll prevention should only be a given scroll event and turned back off afterward
_freezeScrolling = NO;
}
// UIScrollViewDelegate
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
// when the layout is redrawn, scrolling animates. this ensures that we are freeing the view to scroll
_freezeScrolling = NO;
}
/**
This method allows for changing of the content offset for a UIScrollView without triggering the scrollViewDidScroll: delegate method.
*/
- (void)repositionScrollView:(UIScrollView *)scrollView newOffset:(CGPoint)offset {
CGRect scrollBounds = scrollView.bounds;
scrollBounds.origin = offset;
scrollView.bounds = scrollBounds;
}
What's also important to note in the code sample above is the last method. Calling any sort of setContentOffset: will actually trigger scrolling, which results in calling scrollViewDidScroll:. So calling setContentOffset: results in an infinite loop. Setting the scroll bounds is the workaround for this.
In a nutshell, we tell the view controller to prevent the UITextView from scrolling when we detect that the user has selected the text for editing. We also store the current content offset (since we know that the position is what we want). If the UITextView tries to scroll, then we hold the content offset in place until the scroll has stopped (which triggers either scrollViewDidEndDecelerating: or scrollViewDidEndScrollingAnimation:). We also unfreeze the scrolling when the user is done editing.
Remember, this is a basic example, so you'll need to tweak the code based on the exact behavior you want.
I was experiencing a similar issue with undesired UITextView scrolling. I finally managed to fix it by resetting the contentSize at the end of my keyboardDidShow:
- (void)keyboardDidShow:(NSNotification *)notification {
textView.contentSize = CGSizeZero;
}
You also will need to register for the keyboard notification, like so:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];
In my case I didn't want any scrolling since I was resetting the frame to the height of the textView's contentSize when textViewDidChange (growing textview inside a UIScrollView).
Try putting in Redraw on the textview instead of Scale to Fill. You still might have to capture the delegate and keep the content offset but it should at least prevent the jump to point (0,0). Also Autoresizes subview must be turned off. It was jumping to top of textview every time on me too and this solved that problem.