Camera overlay stays closed after navigation - objective-c

I am working with an application that has three screens.
At first is the home screen. From the home screen with a button the user navigates to the second screen where I have a camera overlay with only a button to take picture.
When the user takes a picture then he can press a button that navigates to the last screen. But when he navigates from the last screen to the home and then goes to the overlay-second screen the camera is closed. The button appears he can take a picture but he cannot see through the camera.
I use the following code to the home screen to initiate the overlay:
if (self.imageView.isAnimating)
self.imageView.stopAnimating;
if ([UIImagePickerController isSourceTypeAvailable:sourceType])
{
[self.overlayViewController setupImagePicker:sourceType];
[self presentModalViewController:self.overlayViewController.imagePickerController animated:YES];
}
Where overlayviewcontroller is the second screen and at the overlayviewcontroller the following code:
- (void)setupImagePicker:(UIImagePickerControllerSourceType)sourceType
{
self.imagePickerController.sourceType = sourceType;
if (sourceType == UIImagePickerControllerSourceTypeCamera)
{
self.imagePickerController.showsCameraControls = NO;
if ([[self.imagePickerController.cameraOverlayView subviews] count] == 0)
{
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 addSubview:self.view];
}
}
}
I would appreciate any help.

The same problem is faced by me and I got one solution to it.
My solution is as below
[self performSelector:#selector(setCameraOverlayView) withObject:Nil afterDelay:2];
instead of directly putting the below line
[self.imagePickerController.cameraOverlayView addSubview:self.view];
in overlayViewController's above method
and put that line in the selector method setCameraOverlayView.
just like below
-(void)setCameraOverlayView
{
[imagePickerController.cameraOverlayView addSubview:self.view];
}
This will solve your problem
Happy Coding :)
EDIT
To init the overlay I use the below code and the init function in overlay is also modified
To init the overlay
self.overlayViewController = [[OverlayViewController alloc] initWithNibName:#"OverlayViewController" bundle:nil images:self.finalData];
The init function in overlay
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil images:(NSArray *)imgArray
{
if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]))
{
subCategories = imgArray;
imagePickerController = [[UIImagePickerController alloc] init];
imagePickerController.delegate = self;
imagePickerController.navigationBarHidden = YES;
}
return self;
}
The array is not required or necessary it is some extra info which I required to do some more with overlay you can omit it.
Happy Coding :)

Related

UIViewController's background color becomes transparent when pushing another UIViewController that contains a PDFView [Obj-c]

I'm currently working on a small app that displays a view controller (PDFViewController) that contains a PDFView when a cell in my first view controller (HomeViewController) is clicked. I set the background color of the PDFViewController to be a light gray color in its initializer, but when I push it to my navigation controller, it still appears transparent (I can still see the cells from the HomeViewController).
Here's an image of what's happening. This is the screen shot of when I move the PDFView downward and slightly to the right.
How can I get it so that the view behind this PDFView is just a light gray color?
This is my initializer for PDFViewController:
- (instancetype)init
{
self = [super init];
if (self) {
self.view.backgroundColor = UIColor.lightGrayColor;
// setup PDF view
self.selctedPDF = PDFView.new;
self.selctedPDF.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.selctedPDF];
[self.selctedPDF.leftAnchor constraintEqualToAnchor:self.view.leftAnchor].active = YES;
[self.selctedPDF.rightAnchor constraintEqualToAnchor:self.view.rightAnchor].active = YES;
[self.selctedPDF.topAnchor constraintEqualToAnchor:self.view.topAnchor].active = YES;
[self.selctedPDF.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES;
}
return self;
}
This is how I'm presenting the PDFViewController:
PDFViewController *viewPDF = [[PDFViewController alloc] init];
viewPDF.selctedPDF.document = [self.pdfs objectAtIndex:indexPath.row].doc;
[self.navigationController pushViewController:viewPDF animated:YES];

XCUIElement not found using app.images when running XCUITest

I have a a XCUITest that fails to find a specific image, yet it is perfectly visible on the screen.(emulators and physical devices)
How can I workaround or make the the UIImage accessible to the tests?
All I want to validate is whether the element is visible and touchable on the screen.
The UIImage is added as a subview to a UITableView - it is placed at the bottom of the TableView.
- (void)viewDidLoad {
[super viewDidLoad];
// portions of code left out here
_aView = [[UIImageView alloc] initWithFrame:CGRectZero];
[[self aView] setContentMode:UIViewContentModeScaleAspectFit];
[[self aView] setImage:[UIImage imageNamed:IMG_NAME]] ;
[[self tableView] addSubview:[self aView]];
The UIImageView is later layed out:
- (void) viewWillLayoutSubviews {
// portions of code left out here
[[self aView] setFrame:CGRectMake(0, MAX([self tableView].contentSize.height - 41 ,[self tableView].bounds.size.height - 41) , 180, 30)];
[[self aView] setCenter:CGPointMake([self tableView].center.x, [self aView].center.y)];
Then when running the test:
let app = XCUIApplication()
//this works
let tapString = localizedString("skip")
let skipButton = app.buttons[tapString].firstMatch
if (skipButton.exists) {
skipButton.tap()
}
let theImage = app.images["img.png"]
let doesExist = theImage.exists //<< it never exists
Also doing a debug and printing all the images
does not show the image. I set a breakpoint at the line with doesExists
and in the debug window I run the command:
po app.images
Many images are found but not the particular image under test.
Is there perhaps an alternative to solve this problem?

IOS8 how to move an active popover

I have developed an app for iOS7 and now trying to update it for iOS8.
Issue i have is the following:
The app screen orientation can be rotated and a few buttons in some cases move drastically. I have a few popovers that point to these buttons, so if a popover is open when screen rotates, button moves, i need the popover to also.
In iOS7 i did this by the following:
When screen rotated i updated the constraints
- (void) updateViewConstraints
{
if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation))
{
self.Button.constant = (CGFloat)10;
}
else
{
self.Button.constant = (CGFloat)5;
}
[super updateViewConstraints];
}
I also move the popover
- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
if(TempDisplayPopoverController == examplePopoverController)
{
[examplePopoverController presentPopoverFromRect:[self ExamplePopoverPosition] inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
}
I initially load the popover
- (void) LoadPopover{
examplePopover = [[examplep alloc] initWithNibName:#"exampleP" bundle:nil];
[examplePopover setDelegate:self];
examplePopoverController = [[UIPopoverController alloc] initWithContentViewController: examplePopover];
[examplePopoverController setDelegate:self];
examplePopoverController.popoverContentSize = examplePopover.view.frame.size;
TempDisplayPopoverController = examplePopoverController;
if ([examplePopoverController isPopoverVisible])
{
[examplePopoverController dismissPopoverAnimated:YES];
}
else
{
[examplePopoverController presentPopoverFromRect:[self ExamplePopoverPosition] inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
}
[self ExamplePopoverPosition] just returns button position.
This all worked fine, i was happy, iPad was happy and all behaved.
Now due to iOS8 i have to change a few bits.
self.interfaceOrientation is depreciated
[examplePopoverController presentPopoverFromRect:[self ExamplePopoverPosition] inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
in didRotateFromInterfaceOrientation throws an error
"Application tried to represent an active popover presentation: <UIPopoverPresentationController: 0x7bf59280>"
I've managed to rectify self.interfaceOrientation by
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self SetUpScreen:toInterfaceOrientation];
}
- (void) SetUpScreen:(UIInterfaceOrientation)toInterfaceOrientation{
if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft ||
toInterfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
self.Button.constant = (CGFloat)10;
}
else
{
self.Button.constant = (CGFloat)5;
}
[super updateViewConstraints];
}
but have no idea how to resolve the popover issue. I have tried
popoverController: willRepositionPopoverToRect: inView:
but just can't to seem to get it to work.
Can anyone advice
Thanks
In iOS 8 you can use -viewWillTransitionToSize:withTransitionCoordinator: to handle screen size (and orientation) changes:
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[_popover dismissPopoverAnimated:NO];
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
// Update your layout for the new size, if necessary.
// Compare size.width and size.height to see if you're in landscape or portrait.
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
[_popover presentPopoverFromRect:[self popoverFrame]
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:NO];
}];
}
When you implement this method, the deprecated rotation methods like willAnimateRotationToInterfaceOrientation: will not be called when running on iOS 8.
When using popoverController:willRepositionPopoverToRect:inView:, when reassigning to the rect parameter, try using:
*rect = myNewRect;
and not:
rect = &presentingRect;
Also, make sure you have properly assigned the popover controller's delegate.
First, you don't need to dismiss and present the popover on rotation. UIPopoverPresentationController does that for you. You don't even need to update sourceView/sourceRect once they are set on creating the popover.
Now, the trick with animate(alongsideTransition: ((UIViewControllerTransitionCoordinatorContext) -> Void)?, completion: ((UIViewControllerTransitionCoordinatorContext) -> Void)? = nil) is that you should update your constraints in alongsideTransition closure, not in completion. This way you ensure that UIPopoverPresentationController has the updated sourceRect when restoring the popover at the end of rotation.
What might seem counter-intuitive is that inside alongsideTransition closure you already have your new layout that you derive your constraints calculation from.
Here's an example in Swift:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { _ in
if self.popover != nil {
// optionally scroll to popover source rect, if inside scroll view
let rect = ...
self.scrollView.scrollRectToVisible(rect, animated: false)
// update source rect constraints
myConstraint.constant = ...
myConstrainedView.setNeedsLayout()
myConstrainedView.layoutIfNeeded()
}
}, completion: nil)
}
Very interesting - I got this to work without updating the position manually. I don't know why this works though.
let buttonContainer = UIView(frame: CGRectMake(0, 0, 44, 44))
let button = UIButton(frame: CGRectMake(0, 0, 44, 44))
buttonContainer.addSubview(button)
view.addSubview(buttonContainer)
popover!.presentPopoverFromRect(button, inView: button.superview!, permittedArrowDirections: .Any, animated: true)
Put the button that the popover is presenting from inside a "container view". Then the popover will automatically adjust location upon orientation change.

How to re-size UITextView when keyboard shown with iOS 7

I have a view controller which contains a full-screen UITextView. When the keyboard is shown I would like to resize the text view so that it is not hidden under the keyboard.
This is a fairly standard approach with iOS, as described in this question:
How to resize UITextView on iOS when a keyboard appears?
However, with iOS 7, if the user taps on the text view in the bottom half of the screen, when the text view resizes, the cursor remains offscreen. The text view only scrolls to bring the cursor into view if when the user hits enter.
I read the docs which talk about this very topic. I translated it into Swift and it worked absolutely beautifully for me.
This is used for a full page UITextView like iMessage.
I am using iOS 8.2 and Swift on XCode 6.2 and here's my code. Just call this setupKeyboardNotifications from your viewDidLoad or other initialization method.
func setupKeyboardNotifications() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWasShown:"), name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillBeHidden:"), name: UIKeyboardWillHideNotification, object: nil)
}
func keyboardWasShown(aNotification:NSNotification) {
let info = aNotification.userInfo
let infoNSValue = info![UIKeyboardFrameBeginUserInfoKey] as NSValue
let kbSize = infoNSValue.CGRectValue().size
let contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0)
codeTextView.contentInset = contentInsets
codeTextView.scrollIndicatorInsets = contentInsets
}
func keyboardWillBeHidden(aNotification:NSNotification) {
let contentInsets = UIEdgeInsetsZero
codeTextView.contentInset = contentInsets
codeTextView.scrollIndicatorInsets = contentInsets
}
Also if you are having issues with the caret being in the right place when rotated check for the orientation change and scroll to the right position.
override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
scrollToCaretInTextView(codeTextView, animated: true)
}
func scrollToCaretInTextView(textView:UITextView, animated:Bool) {
var rect = textView.caretRectForPosition(textView.selectedTextRange?.end)
rect.size.height += textView.textContainerInset.bottom
textView.scrollRectToVisible(rect, animated: animated)
}
Swift 3:
func configureKeyboardNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown(aNotification:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(aNotification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
func keyboardWasShown(aNotification:NSNotification) {
let info = aNotification.userInfo
let infoNSValue = info![UIKeyboardFrameBeginUserInfoKey] as! NSValue
let kbSize = infoNSValue.cgRectValue.size
let contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0)
textView.contentInset = contentInsets
textView.scrollIndicatorInsets = contentInsets
}
func keyboardWillBeHidden(aNotification:NSNotification) {
let contentInsets = UIEdgeInsets.zero
textView.contentInset = contentInsets
textView.scrollIndicatorInsets = contentInsets
}
Swift 4 & 5:
func setupKeyboardNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_ :)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
#objc func keyboardWillShow(_ notification:NSNotification) {
let d = notification.userInfo!
var r = (d[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
r = self.textView.convert(r, from:nil)
self.textView.contentInset.bottom = r.size.height
self.textView.verticalScrollIndicatorInsets.bottom = r.size.height
}
#objc func keyboardWillHide(_ notification:NSNotification) {
let contentInsets = UIEdgeInsets.zero
self.textView.contentInset = contentInsets
self.textView.verticalScrollIndicatorInsets = contentInsets
}
With Auto Layout, it's much easier (provided you understand Auto Layout) to handle:
Instead of trying to identify and resize the affected views, you simply create a parent frame for all your view's contents. Then, if the kbd appears, you resize the frame, and if you've set up the constraints properly, the view will re-arrange all its child views nicely. No need to fiddle with lots of hard-to-read code for this.
In fact, in a similar question I found a link to this excellent tutorial about this technique.
Also, the other examples here that do use textViewDidBeginEditing instead of the UIKeyboardWillShowNotification have one big issue:
If the user has an external bluetooth keyboard attached then the control would still get pushed up even though no on-screen keyboard appears. That's not good.
So, to summarize:
Use Auto Layout
Use the UIKeyboardWillShowNotification notification,
not the TextEditField's events for deciding when to resize your
views.
Alternatively, check out LeoNatan's reply. That might even be a cleaner and simpler solution (I've not tried myself yet).
Do not resize the text view. Instead, set the contentInset and scrollIndicatorInsets bottom to the keyboard height.
See my answer here:
https://stackoverflow.com/a/18585788/983912
Edit
I made the following changes to your sample project:
- (void)textViewDidBeginEditing:(UITextView *)textView
{
_caretVisibilityTimer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:#selector(_scrollCaretToVisible) userInfo:nil repeats:YES];
}
- (void)_scrollCaretToVisible
{
//This is where the cursor is at.
CGRect caretRect = [self.textView caretRectForPosition:self.textView.selectedTextRange.end];
if(CGRectEqualToRect(caretRect, _oldRect))
return;
_oldRect = caretRect;
//This is the visible rect of the textview.
CGRect visibleRect = self.textView.bounds;
visibleRect.size.height -= (self.textView.contentInset.top + self.textView.contentInset.bottom);
visibleRect.origin.y = self.textView.contentOffset.y;
//We will scroll only if the caret falls outside of the visible rect.
if(!CGRectContainsRect(visibleRect, caretRect))
{
CGPoint newOffset = self.textView.contentOffset;
newOffset.y = MAX((caretRect.origin.y + caretRect.size.height) - visibleRect.size.height + 5, 0);
[self.textView setContentOffset:newOffset animated:NO];
}
}
Removed setting old caret position at first, as well as disabled animation. Now seems to work well.
Whilst the answer given by #Divya lead me to the correct solution (so I awarded the bounty), it is not a terribly clear answer! Here it is in detail:
The standard approach to ensuring that a text view is not hidden by the on-screen keyboard is to update its frame when the keyboard is shown, as detailed in this question:
How to resize UITextView on iOS when a keyboard appears?
However, with iOS 7, if you change the text view frame within your handler for the UIKeyboardWillShowNotification notification, the cursor will remain off screen as described in this question.
The fix for this issue is to change the text view frame in response to the textViewDidBeginEditing delegate method instead:
#implementation ViewController {
CGSize _keyboardSize;
UITextView* textView;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
textView = [[UITextView alloc] initWithFrame:CGRectInset(self.view.bounds, 20.0, 20.0)]; textView.delegate = self;
textView.returnKeyType = UIReturnKeyDone;
textView.backgroundColor = [UIColor greenColor];
textView.textColor = [UIColor blackColor];
[self.view addSubview:textView];
NSMutableString *textString = [NSMutableString new];
for (int i=0; i<100; i++) {
[textString appendString:#"cheese\rpizza\rchips\r"];
}
textView.text = textString;
}
- (void)textViewDidBeginEditing:(UITextView *)textView1 {
CGRect textViewFrame = CGRectInset(self.view.bounds, 20.0, 20.0);
textViewFrame.size.height -= 216;
textView.frame = textViewFrame;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
CGRect textViewFrame = CGRectInset(self.view.bounds, 20.0, 20.0);
textView.frame = textViewFrame;
[textView endEditing:YES];
[super touchesBegan:touches withEvent:event];
}
#end
NOTE: unfortunately textViewDidBeginEdting fires before the UIKeyboardWillShowNotification notification, hence the need to hard-code the keyboard height.
Following on is working for me :
.h file
#interface ViewController : UIViewController <UITextViewDelegate> {
UITextView *textView ;
}
#property(nonatomic,strong)IBOutlet UITextView *textView;
#end
.m file
#implementation ViewController
#synthesize textView;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
CGRect textViewFrame = CGRectMake(20.0f, 20.0f, 280.0f, 424.0f);
//UITextView *textView = [[UITextView alloc] initWithFrame:textViewFrame];
textView.frame = textViewFrame;
textView.delegate = self;
textView.returnKeyType = UIReturnKeyDone;
textView.backgroundColor = [UIColor greenColor];
textView.textColor = [UIColor blackColor];
[self.view addSubview:textView];
}
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView{
NSLog(#"textViewShouldBeginEditing:");
return YES;
}
- (void)textViewDidBeginEditing:(UITextView *)textView1 {
NSLog(#"textViewDidBeginEditing:");
CGRect textViewFrame = CGRectMake(20.0f, 20.0f, 280.0f, 224.0f);
textView1.frame = textViewFrame;
}
- (BOOL)textViewShouldEndEditing:(UITextView *)textView{
NSLog(#"textViewShouldEndEditing:");
return YES;
}
- (void)textViewDidEndEditing:(UITextView *)textView{
NSLog(#"textViewDidEndEditing:");
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text{
return YES;
}
- (void)textViewDidChange:(UITextView *)textView{
NSLog(#"textViewDidChange:");
}
- (void)textViewDidChangeSelection:(UITextView *)textView{
NSLog(#"textViewDidChangeSelection:");
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(#"touchesBegan:withEvent:");
CGRect textViewFrame = CGRectMake(20.0f, 20.0f, 280.0f, 424.0f);
textView.frame = textViewFrame;
[self.view endEditing:YES];
[super touchesBegan:touches withEvent:event];
}
#end
i had done it and its work completely.
#define k_KEYBOARD_OFFSET 95.0
-(void)keyboardWillAppear {
// Move current view up / down with Animation
if (self.view.frame.origin.y >= 0)
{
[self moveViewUp:NO];
}
else if (self.view.frame.origin.y < 0)
{
[self moveViewUp:YES];
}
}
-(void)keyboardWillDisappear {
if (self.view.frame.origin.y >= 0)
{
[self moveViewUp:YES];
}
else if (self.view.frame.origin.y < 0)
{
[self moveViewUp:NO];
}
}
-(void)textFieldDidBeginEditing:(UITextField *)sender
{
//if ([sender isEqual:_txtPassword])
// {
//move the main view up, so the keyboard will not hide it.
if (self.view.frame.origin.y >= 0)
{
[self moveViewUp:YES];
}
//}
}
//Custom method to move the view up/down whenever the keyboard is appeared / disappeared
-(void)moveViewUp:(BOOL)bMovedUp
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.4]; // to slide the view up
CGRect rect = self.view.frame;
if (bMovedUp) {
// 1. move the origin of view up so that the text field will come above the keyboard
rect.origin.y -= k_KEYBOARD_OFFSET;
// 2. increase the height of the view to cover up the area behind the keyboard
rect.size.height += k_KEYBOARD_OFFSET;
} else {
// revert to normal state of the view.
rect.origin.y += k_KEYBOARD_OFFSET;
rect.size.height -= k_KEYBOARD_OFFSET;
}
self.view.frame = rect;
[UIView commitAnimations];
}
- (void)viewWillAppear:(BOOL)animated
{
// register keyboard notifications to appear / disappear the keyboard
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillAppear)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillDisappear)
name:UIKeyboardWillHideNotification
object:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
// unregister for keyboard notifications while moving to the other screen.
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillHideNotification
object:nil];
}
This is my solution, July 2015 using Swift 1.2 on Xcode 6.4 targeting iOS 7.1 - a combination of several approaches. Borrowed Johnston's keyboard handing Swift code. Its a bit of a hack, but its simple and it works.
I have a vanilla UITextView inside a single View.
I did not want to embed it inside a UIScrollView as per Apple's documentation. I just wanted the UITextView re-sized when software keyboard appeared, and resized to original when keyboard was dismissed.
These are the basic steps:
Set up keyboard notifications
Set up layout constraint in "Interface Builder" (TextView to bottom edge in my case)
Create an IBOutlet for this constraint in the relevant code file so you can adjust it programmatically
Use keyboard notifications to intercept events and get keyboard size
Programmatically adjust constraint IBOutlet using keyboard size to re-size TextView.
Put everything back when keyboard is dismissed.
So, onto the code.
I've set up constraint outlet at the top of the code file via the usual drag-drop in interface builder: #IBOutlet weak var myUITextViewBottomConstraint: NSLayoutConstraint!
I also set up a global variable where I can back up the state of affairs before the keyboard come up: var myUITextViewBottomConstraintBackup: CGFloat = 0
Implement keyboard notifications, call this function in viewDidLoad or any other startup/setup section:
func setupKeyboardNotifications() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWasShown:"), name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillBeHidden:"), name: UIKeyboardWillHideNotification, object: nil)
}
Then these two functions will be called automatically when keyboard is shown/dismissed:
func keyboardWasShown(aNotification:NSNotification) {
let info = aNotification.userInfo
let infoNSValue = info![UIKeyboardFrameBeginUserInfoKey] as! NSValue
let kbSize = infoNSValue.CGRectValue().size
let newHeight = kbSize.height
//backup old constraint size
myUITextViewBottomConstraintOld = myUITextViewBottomConstraint.constant
// I subtract 50 because otherwise it leaves a gap between keyboard and text view. I'm sure this could be improved on.
myUITextViewBottomConstraint.constant = newHeight - 50
func keyboardWillBeHidden(aNotification:NSNotification) {
//restore to whatever AutoLayout set it before you messed with it
myUITextViewBottomConstraint.constant = myUITextViewBottomConstraintOld
}
The code works, with a minor issue:
It's not responsive to the predictive text ribbon above the keyboard opening/closing. I.e. it will take the state of it into account when the keyboard is called up, but if you were to slide it up or down while keyboard is shown the constraint will not be adjusted. It is a separate event that needs to be handled. Its not enough of a functionality hit for me to bother with.
#Johnston found a good solution. Here's a variation using UIKeyboardWillChangeFrameNotification which correctly accounts for keyboard size changes (i.e. showing/hiding the QuickType bar). It also correctly handles the case where the text view is embedded in a navigation controller (i.e. where the contentInset isn't otherwise zero). It's also written in Swift 2.
override func viewDidLoad() {
:
NSNotificationCenter.defaultCenter().addObserverForName(UIKeyboardWillChangeFrameNotification, object: nil, queue: nil) { (notification) -> Void in
guard let userInfo = notification.userInfo,
let keyboardFrameEndValue = userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue
else { return }
let windowCoordinatesKeyboardFrameEnd = keyboardFrameEndValue.CGRectValue() // window coordinates
let keyboardFrameEnd = self.view.convertRect(windowCoordinatesKeyboardFrameEnd, fromView: nil) // view coordinates
var inset = self.textView.contentInset
inset.bottom = CGRectGetMaxY(self.textView.frame) - CGRectGetMinY(keyboardFrameEnd) // bottom inset is the bottom of textView minus top of keyboard
self.textView.contentInset = inset
self.textView.scrollIndicatorInsets = inset
}
}

MKMapView Overlay (image animation)

I'm trying to animate a radar image on an MKMapView. I have separate images for different times, and I have successfully set an image as an overlay on top of the MKMapView. My problem is when I try to show these different images in a sequence one after each other. I tried a method of adding and removing overlays, but the system does not handle it well at all, with flickering of overlays, and some overlays not being removed, and overall, its not worth it.
Could anyone help me find a way to show multiple images (like an animated gif) on top of a MapView in a way that is smooth and fast?
Here's my code to overlay the image:
- (id)initWithImageData:(NSData*)imageData withLowerLeftCoordinate:(CLLocationCoordinate2D)lowerLeftCoordinate withUpperRightCoordinate:(CLLocationCoordinate2D)upperRightCoordinate {
self.radarData = imageData;
MKMapPoint lowerLeft = MKMapPointForCoordinate(lowerLeftCoordinate);
MKMapPoint upperRight = MKMapPointForCoordinate(upperRightCoordinate);
mapRect = MKMapRectMake(lowerLeft.x, upperRight.y, upperRight.x - lowerLeft.x, lowerLeft.y - upperRight.y);
return self;
}
- (CLLocationCoordinate2D)coordinate
{
return MKCoordinateForMapPoint(MKMapPointMake(MKMapRectGetMidX(mapRect), MKMapRectGetMidY(mapRect)));
}
- (MKMapRect)boundingMapRect
{
return mapRect;
}
and here's how I'm setting it up on the MapView:
- (void)drawMapRect:(MKMapRect)mapRect
zoomScale:(MKZoomScale)zoomScale
inContext:(CGContextRef)ctx
{
MapOverlay *mapOverlay = (MapOverlay *)self.overlay;
image = [[UIImage alloc] initWithData:mapOverlay.radarData];
MKMapRect theMapRect = [self.overlay boundingMapRect];
CGRect theRect = [self rectForMapRect:theMapRect];
#try {
UIGraphicsBeginImageContext(image.size);
UIGraphicsPushContext(ctx);
[image drawInRect:theRect blendMode:kCGBlendModeNormal alpha:0.5];
UIGraphicsPopContext();
UIGraphicsEndImageContext();
}
#catch (NSException *exception) {
NSLog(#"Caught an exception while drawing radar on map - %#",[exception description]);
}
#finally {
}
}
and here's where I'm adding the overlay:
- (void)mapRadar {
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
self.mapOverlay = [[MapOverlay alloc] initWithImageData:appDelegate.image withLowerLeftCoordinate:CLLocationCoordinate2DMake(appDelegate.south, appDelegate.west) withUpperRightCoordinate:CLLocationCoordinate2DMake(appDelegate.north, appDelegate.east)];
[self.mapView addOverlay:self.mapOverlay];
[self.mapView setNeedsDisplay];
self.mapView.showsUserLocation = YES;
MKMapPoint lowerLeft2 = MKMapPointForCoordinate(CLLocationCoordinate2DMake(appDelegate.south2, appDelegate.west2) );
MKMapPoint upperRight2 = MKMapPointForCoordinate(CLLocationCoordinate2DMake(appDelegate.north2, appDelegate.east2));
MKMapRect localMapRect = MKMapRectMake(lowerLeft2.x, upperRight2.y, upperRight2.x - lowerLeft2.x, lowerLeft2.y - upperRight2.y);
[self.mapView setVisibleMapRect:localMapRect animated:YES];
}
#pragma Mark - MKOverlayDelgateMethods
-(MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id)overlay{
if([overlay isKindOfClass:[MapOverlay class]]) {
MapOverlay *mapOverlay = overlay;
self.mapOverlayView = [[MapOverlayView alloc] initWithOverlay:mapOverlay];
}
return self.mapOverlayView;
}
Does anyone have an idea or a solution where I can show a sequence of images on an MKMapView smoothly? At this point, I'm desperate for solutions and can't find it anywhere else, so anything would help me, thanks!
Using Lefteris' suggestion, you can just use UIImageView with animationImages. The animation should be smooth provided your overlay images are properly sized for the screen.
To pass touches through to the map view below, you can create a new UIView subclass with a (weak) reference to the map view. Override the touch event methods (touchesBegan:withEvent:, touchesMoved:withEvent:, etc) to forward to the map view, like so:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.mapView touchesBegan:touches withEvent:event];
}
This way the user can still interact with the view below.
You may run into problems when the user tries to zoom, as it will be difficult if not impossible to scale all your animation frames in real-time. You should look into using CATiledLayer instead.