iOS 9 UITextField textRectForBounds issue - cocoa-touch

I am running on iOS 9, XCode 7 GM. I have a class that extends UITextField (CustomOffsetTextField) and provides support for custom text positioning like so:
override func textRectForBounds(bounds: CGRect) -> CGRect {
return CGRectOffset(bounds, textOffset.x + leftViewOffset, textOffset.y)
}
override func placeholderRectForBounds(bounds: CGRect) -> CGRect {
return CGRectOffset(bounds, textOffset.x + leftViewOffset, textOffset.y)
}
override func editingRectForBounds(bounds: CGRect) -> CGRect {
return CGRectOffset(bounds, textOffset.x + leftViewOffset, textOffset.y)
}
leftViewOffset is the width of the text field's leftView, if one exists. textOffset is a CGPoint that defines custom x and y offsets to apply to the text rects.
My issue is occurring on my sign in view. I have 2 instances of my CustomOffsetTextField - one for the user's e-mail and one for their password.
On first load of the view controller, if I enter text into one field and then tap into the other field, that text will jump back to position 0,0 in its text field briefly before jumping back to the position that is defined by textRectForBounds. Some basic print debugging verifies that these functions are always returning the values that I expect them to.
After this initial hiccup, the text field then behaves as I would expect it to. This issue only occurs one time in each text field after the view controller loads. After that, I can tap back and forth between the fields as much as I want without it happening again.
Has anyone seen similar issues with UITextField in iOS 9? If so, were you able to find a fix?

In iOS 9 an extra UIKeyboardWillShowNotification notification is sent whenever you tap between the text fields. If you have a call to [self.view layoutIfNeeded] in the notification callback it will cause the jump.
// Animate
[UIView beginAnimations:#"keyboardDidShowAnimations" context:NULL];
[UIView setAnimationDuration:duration];
[UIView setAnimationCurve:curve];
[self.view layoutIfNeeded];
[UIView commitAnimations];
It's related to this:
https://forums.developer.apple.com/message/53905#53905
If you're testing in the simulator without the software keyboard, you'll get an extra UIKeyboardWillHideNotification instead which will likely cause the same issue if you have a layoutIfNeeded call in that notification callback as well.
I resolved this by putting checks at the top of the callbacks to ensure that I really needed to animate/update constraints.
- (void)keyboardWillShow:(NSNotification *)note {
BOOL shouldAnimate = self.someConstraint.constant != kMinimumSize;
if (shouldAnimate) {
...
- (void)keyboardWillHide:(NSNotification *)note {
BOOL shouldAnimate = self.someConstraint.constant == kMinimumSize;
if (shouldAnimate) {
...
Update:
This won't necessarily work properly with 3rd party keyboards which call the notification methods multiple times. See https://stackoverflow.com/a/26004605.

Related

Added whitespace in UIWebview - removing UIWebView whitespace in iOS7 & iOS8

Im loading local html files, since iOS7 there is added white space on top in the UIWebView.(I cant post an image as i do not have enough points.)
image can be seen here- snap shot from iPhone simulator, uiwebview surrounded by black frame, the html content is grey, but there is white added above it
I have tried to adjust the zoom using
[webView stringByEvaluatingJavaScriptFromString:#"document. body.style.zoom = 5.0;"];
webView.scalesPageToFit = NO;
credit to: Srikar Appal
I also set tried to remove white spacing:
NSString *padding = #"document.body.style.margin='0';document.body.style.padding = '0'";
[webView stringByEvaluatingJavaScriptFromString:padding];
credit to: thenextmillionaire
still no luck. In the desktop chrome browser there is no whitespace. The html files are Google Swiffy files - containing html and JSON.
edit: updated Image
Try self.automaticallyAdjustsScrollViewInsets = NO; in ViewDidLoad.
ios 7 add 64px automatically for scroll view. (status bar and nav bar)
This problem only affects the UIWebView if it is the first subview of the parent view. One alternative way to work around this problem is to add another non-visible empty view to the parent view as the first view. In Interface Builder add a zero size subview and use the Editor->Arrange->Send to Back menu command.
If you're not using Interface Builder, but instead are subclassing the UIWebView, then it can be done by creating a UIView instance variable called scrollFixView and overriding the following methods:
- (void)didMoveToSuperview
{
[super didMoveToSuperview];
if ([self superview].subviews.firstObject == self) {
_scrollFixView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
_scrollFixView.hidden = YES;
[[self superview] insertSubview:_scrollFixView belowSubview:self];
}
}
- (void)removeFromSuperview
{
if (_scrollFixView) {
[_scrollFixView removeFromSuperview];
_scrollFixView = nil;
}
[super removeFromSuperview];
}
I had the same problem so I tried a few things:-)
This worked for me, but correct me please if there is a better way.
-(void)webViewDidFinishLoad:(UIWebView *)webView
{
if(self.navigationController.navigationBar.translucent == YES)
{
_webView.scrollView.contentOffset = CGPointMake(_webView.frame.origin.x, _webView.frame.origin.y - 54);
}
}
So basically you need to :
1) Add the UIWebView delegate method - webViewDidFinishLoad:
2) Then I setup an if statement to check if the translucent option is active.
The last one you only need to do of course if you give the user the option within your app.
The number after the _webView.frame.origin.y is just for my app. It may differ for you.
I solved this problem by simply setting a constraint on the WebView, setting the top space between it and the View top to 0, causing the NavBar to overlap the whitespace.
One alternative to Jeff Kranenburg's method is to subclass and override the UIWebView subclasses' UIScrollViewDelegate method scrollViewDidScroll:. This is only appropriate if scrolling is turned off for your subclass.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if ([[self superclass] instancesRespondToSelector:_cmd]) {
[super scrollViewDidScroll:scrollView];
}
[self fixUpScrollViewContentOffset];
}
- (void)fixUpScrollViewContentOffset
{
if (!CGPointEqualToPoint(self.scrollView.contentOffset, CGPointZero)) {
self.scrollView.contentOffset = CGPointZero;
}
}
I already got it .
here my logic code, When the application open the website you must get the size of your webview then set it on height
here my code
ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) webpage.getLayoutParams();
p.height = webpage.getHeight();
// check if how long you need to set your height for webpage then set it :)
Log.e(" webpage.getHeight()", String.valueOf(webpage.getHeight()));
webpage.setLayoutParams(p);
Hope you will take my code and my answer to :) works on any devices even tabs too :)

Animating a view & its subview separately

I have a view interface which contains a subview touchWheel. On clicking a minimise button on interface I'd like to perform a transform & send the view to the bottom-right corner of my screen. The thing is that I want to "detach" the touchWheel view & send it to the corner separately with the parent-view, interface, following behind (& in fact fading out).
when I try this I encounter a problem. My animation works fine initially & touchWheel is sent towards the bottom corner, as desired. When touchWheel is about half-way to its destination I animate interface so that it follows touchWheel.
Once interface starts animating it appears to control touchWheel & touchWheel changes course.
It seems that interface still retains control of touchWheel since it's its parent view.
- (void)hideInterfaceButtonClicked : (id) sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"RemoveGrey" object:self];
//Remove Views & transform
[touchWheel removeViews];
interfaceHidden = YES;
//Send Interface to corner
[UIView animateWithDuration:1.25
delay:0.0
options:UIViewAnimationCurveEaseIn
animations:^{
// Move to the right
CGAffineTransform translateInterface = CGAffineTransformMakeTranslation(437,200);
// Scale
CGAffineTransform scale = CGAffineTransformMakeScale(0.135,0.135);
// Apply them to the view
self.transform = CGAffineTransformConcat(scale, translateInterface);
self.alpha = 0.0;
} completion:^(BOOL finished) {
NSLog(#"Animation Has Stopped");
[[NSNotificationCenter defaultCenter] postNotificationName:#"hiddenInterfaceViewNeeded" object:self]; //after MoveView finishes
}];
}
Making touchWheel subview of something other than interface creates other problems which would complicate things further.
Any ideas how one might get around this issue? any tips greatly appreciated :)
You could do the following:
Move touchWheel to the interface's superview, recalculating its frame using convertRect:toView: so that touchWheel appears to have the same window coordinates.
Animate both the views to their final locations.
In the completion block of the animation do the opposite of step 1: make touchWheel a subview of interface, recalculating its frame again.
It doesn't seem too complicated, but there's a caveat: you may want to temporarily disable the maximize button (or whatever the appropriate control is) to prevent the maximize action during the animation. Otherwise, this approach will cause unpredictable results.

MPMoviePlayerController adding UIButton to view that fades with controls

I am trying to add a UIButton to the view of a MPMoviePlayerController along with the standard controls. The button appears over the video and works as expected receiving touch events, but I would like to have it fade in and out with the standard controls in response to user touches.
I know I could accomplish this by rolling my own custom player controls, but it seems silly since I am just trying to add one button.
EDIT
If you recursively traverse the view hierarchy of the MPMoviePlayerController's view eventually you will come to a view class called MPInlineVideoOverlay. You can add any additional controls easily to this view to achieve the auto fade in/out behavior.
There are a few gotchas though, it can sometimes take awhile (up to a second in my experience) after you have created the MPMoviePlayerController and added it to a view before it has initialized fully and created it's MPInlineVideoOverlay layer. Because of this I had to create an instance variable called controlView in the code below because sometimes it doesn't exist when this code runs. This is why I have the last bit of code where the function calls itself again in 0.1 seconds if it isn't found. I couldn't notice any delay in the button appearing on my interface despite this delay.
-(void)setupAdditionalControls {
//Call after you have initialized your MPMoviePlayerController (probably viewDidLoad)
controlView = nil;
[self recursiveViewTraversal:movie.view counter:0];
//check to see if we found it, if we didn't we need to do it again in 0.1 seconds
if(controlView) {
UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
[controlView addSubview:backButton];
} else {
[self performSelector:#selector(setupAdditionalControls) withObject:nil afterDelay:0.1];
}
}
-(void)recursiveViewTraversal:(UIView*)view counter:(int)counter {
NSLog(#"Depth %d - %#", counter, view); //For debug
if([view isKindOfClass:NSClassFromString(#"MPInlineVideoOverlay")]) {
//Add any additional controls you want to have fade with the standard controls here
controlView = view;
} else {
for(UIView *child in [view subviews]) {
[self recursiveViewTraversal:child counter:counter+1];
}
}
}
It isn't the best solution, but I am posting it in case someone else is trying to do the same thing. If Apple was to change the view structure or class names internal to the control overlay it would break. I am also assuming you aren't playing the video full screen (although you can play it fullscreen with embeded controls). I also had to disable the fullscreen button using the technique described here because the MPInlineVideoOverlay view gets removed and released when it is pressed: iPad MPMoviePlayerController - Disable Fullscreen
Calling setupAdditionalControls when you receive the fullscreen notifications described above will re-add your additional controls to the UI.
Would love a more elegant solution if anyone can suggest something other than this hackery I have come up with.
My solution to the same problem was:
Add the button as a child of the MPMoviePlayerController's view;
fade the button in and out using animation of its alpha property, with the proper durations;
handle the player controller's touchesBegan, and use that to toggle the button's visibility (using its alpha);
use a timer to determine when to hide the button again.
By trial-and-error, I determined that the durations that matched the (current) iOS ones are:
fade in: 0.1s
fade out: 0.2s
duration on screen: 5.0s (extend that each time the view is touched)
Of course this is still fragile; if the built-in delays change, mine will look wrong, but the code will still run.

How would I develop something like DeskLock from Deskshade?

Sorry to be a nuisance, but I have yet ANOTHER question. How would I do something like DeskLock from macrabbit's Deskshade app? I've made the little window and that's as far as I've come. I know how to "lock" the screen in 10.6 with PresentationOptions, but I don't want to risk it because last time it wouldn't let me back in ;]
EDIT: The DeskShade app actually is meant to cover your desktop, hiding all icons. It also allows you to randomize wallpaper patterns with several fade/swipes. There is one extra feature called DeskLock that actually presents a translucent black bevel (similar to AppSwitcher build into Mac) with a lock icon, and you can place personal text. When you click the lock icon, it presents a modal that asks for a password you can set. You can also just type this password without pressing anything, followed by the Enter key, and it unlocks the screen. This uses the DeskShade feature of hiding the desktop as well.
Thanks!
To create the overlay window you have to subclass NSWindow and set its style mask and background color:
#implementation BigTransparentWindow
- (id)initWithContentRect:(NSRect)contentRect
styleMask:(NSUInteger)windowStyle
backing:(NSBackingStoreType)bufferingType
defer:(BOOL)deferCreation
{
self = [super initWithContentRect:contentRect
styleMask:NSBorderlessWindowMask //this makes the window transparent
backing:bufferingType
defer:deferCreation];
if(self)
{
[self setOpaque:NO];
[self setHasShadow:NO];
[self setBackgroundColor:[[NSColor blackColor] colorWithAlphaComponent:0.5]];
}
return self;
}
#end
You then need to set the window's frame so that it covers all screens, and you need to set its window level appropriately:
- (IBAction)showWindow:(id)sender
{
//set the window so it covers all available screens
NSRect screensRect = NSZeroRect;
for(NSScreen* screen in [NSScreen screens])
{
screensRect = NSUnionRect(screensRect,[screen frame]);
}
[yourWindow setFrame:screensRect display:YES];
if(coverScreen)
{
//set the window so it is above all other windows
[yourWindow setLevel:kCGMaximumWindowLevel];
}
else
{
//set the window so it sits just above the desktop icons
[yourWindow setLevel:kCGDesktopIconWindowLevel + 1];
}
}
As you've mentioned, you can use the NSApplicationPresentationOptions settings for NSApp to control how the user can interact with the system. An easy way to test this without locking yourself out is to set an NSTimer that calls a method that pulls the app out of kiosk mode after a timeout period.

Why doesn't UIView.exclusiveTouch work?

In one of my iPhone projects, I have three views that you can move around by touching and dragging. However, I want to stop the user from moving two views at the same time, by using two fingers. I have therefore tried to experiment with UIView.exclusiveTouch, without any success.
To understand how the property works, I created a brand new project, with the following code in the view controller:
- (void)loadView {
self.view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
UIButton* a = [UIButton buttonWithType:UIButtonTypeInfoDark];
[a addTarget:self action:#selector(hej:) forControlEvents:UIControlEventTouchUpInside];
a.center = CGPointMake(50, 50);
a.multipleTouchEnabled = YES;
UIButton* b = [UIButton buttonWithType:UIButtonTypeInfoDark];
[b addTarget:self action:#selector(hej:) forControlEvents:UIControlEventTouchUpInside];
b.center = CGPointMake(200, 50);
b.multipleTouchEnabled = YES;
a.exclusiveTouch = YES;
[self.view addSubview:a];
[self.view addSubview:b];
}
- (void)hej:(id)sender
{
NSLog(#"hej: %#", sender);
}
When running this, hej: gets called, with different senders, when pressing any of the buttons - even though one of them has exclusiveTouch set to YES. I've tried commenting the multipleTouchEnabled-lines, to no avail. Can somebody explain to me what I'm missing here?
Thanks,
Eli
From The iPhone OS Programming Guide:
Restricting event delivery to a single view:
By default, a view’s exclusiveTouch property is set to NO. If you set
the property to YES, you mark the view so that, if it is tracking
touches, it is the only view in the window that is tracking touches.
Other views in the window cannot receive those touches. However, a
view that is marked “exclusive touch” does not receive touches that
are associated with other views in the same window. If a finger
contacts an exclusive-touch view, then that touch is delivered only if
that view is the only view tracking a finger in that window. If a
finger touches a non-exclusive view, then that touch is delivered only
if there is not another finger tracking in an exclusive-touch view.
It states that the exclusive touch property does NOT affect touches outside the frame of the view.
To handle this in the past, I use the main view to track ALL TOUCHES on screen instead of letting each subview track touches. The best way is to do:
if(CGRectContainsPoint(thesubviewIcareAbout.frame, theLocationOfTheTouch)){
//the subview has been touched, do what you want
}
I was encountering an issue like this where taps on my UIButtons were getting passed through to a tap gesture recognizer that I had attached to self.view, even though I was setting isExclusiveTouch to true on my UIButtons. Upon reviewing the materials here so far, I decided to put some code in my tap gesture code that checks if the tap location is contained in any UIButton frame and if that frame is also visible on the screen at the same time. If both of those conditions are true, then the UIButton will already have handled the tap, and the event triggered in my gesture recognizer can then be ignored as a pass through of the event. My logic allows me to loop over all subviews, checking if they are of type UIButton, and then checking if the tap was in that view and the view is visible.
#objc func singleTapped(tap: UITapGestureRecognizer)
{
anyControlsBreakpoint()
let tapPoint = tap.location(in: self.view)
// Prevent taps inside of buttons from passing through to singleTapped logic
for subview in self.view.subviews
{
if subview is UIButton {
if pointIsInFrameAndThatFrameIsVisible(view: subview, point: tapPoint)
{
return // Completely ignores pass through events that were already handled by a UIButton
}
}
}
Below is the code that checks if point was inside a visible button. Note that I hide my buttons by setting their alpha to zero. If you are using the isHidden property, your logic might need to look for that.
func pointIsInFrameAndThatFrameIsVisible(view : UIView, point : CGPoint) -> Bool
{
return view.frame.contains(point) && view.alpha == 1
}