Related
Currently, I know how to create a MKAnnotationView with a static pin, an image that's added.
Does anyone know, or have any resources, on how to create a pin that would change colors or have a number displayed inside of it that would change depending on information about the business?
For instance, I would like to have a pin be red when the business is closed, and green when the business is open. Maybe even a dollar signs inside of the pin to tell the user how expensive it is.
EDIT
I have created a class called CustomPin that adopts the MKAnnotation protocol. Additionally, I'm not looking to have a custom MKAnnotationView image that can change. Does that mean I'll have to add multiple images for the MKAnnotationView in an array and have it change the image everytime the details about the business change?
Thank you in advance!
You can create custom MKAnnotationView by simply inheritance. You can use this class to create annotation view from delegate method.
Here is one example.
class KDAnnotationView: MKAnnotationView {
let titleLabel = UILabel()
convenience init(annotation: MKAnnotation?) {
self.init(annotation: annotation, reuseIdentifier: "indetifier")
self.canShowCallout = false
self.frame = CGRectMake(0, 0, 75.0, 85.0)
self.backgroundColor = UIColor.clearColor()
self.centerOffset = CGPointMake(0, 0)
self.titleLabel.backgroundColor = UIColor.clearColor()
self.titleLabel.textColor = UIColor.blackColor()
self.titleLabel.font = UIFont.systemFontOfSize(16.0)
self.addSubview(self.titleLabel)
}
override func layoutSubviews() {
super.layoutSubviews()
var frame = CGRectInset(self.bounds, 5.0, 5.0)
frame.size.height = 20.0
self.titleLabel.frame = frame
}
override func drawRect(rect: CGRect) {
super.drawRect(rect)
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: CGRectGetMinX(rect), y: CGRectGetMinY(rect)))
path.addLineToPoint(CGPoint(x: CGRectGetMaxX(rect), y: CGRectGetMinY(rect)))
path.addLineToPoint(CGPoint(x: CGRectGetMaxX(rect), y: CGRectGetMaxY(rect) - 10.0))
path.addLineToPoint(CGPoint(x: CGRectGetMidX(rect) + 5.0, y: CGRectGetMaxY(rect) - 10.0))
path.addLineToPoint(CGPoint(x: CGRectGetMidX(rect), y: CGRectGetMaxY(rect)))
path.addLineToPoint(CGPoint(x: CGRectGetMidX(rect) - 5.0, y: CGRectGetMaxY(rect) - 10.0))
path.addLineToPoint(CGPoint(x: CGRectGetMinX(rect), y: CGRectGetMaxY(rect) - 10.0))
path.closePath()
UIColor.lightGrayColor().setStroke()
UIColor.whiteColor().setFill()
path.stroke()
path.fill()
}
//MARK: - Public Methods
func setText(text:String) {
self.titleLabel.text = text
}
}
You have to do this programmatically. Following code may help you:
Initialize the pin and give it a type, such as opened, closed, expensive etc.
MapViewAnnotation *newAnnotation = [[MapViewAnnotation alloc] init...
newAnnotation.type = #"opened"; // <-- set type in annotation
[self.mapView addAnnotation:newAnnotation];
In your (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id ) annotation method put a simple check
MapViewAnnotation *mvAnn = (MapViewAnnotation *)annotation;
if ([mvAnn.type isEqualToString:#"opened"])
{
annView.pinColor = MKPinAnnotationColorGreen;
}
else if ([mvAnn.type isEqualToString:#"closed"])
{
annView.pinColor = MKPinAnnotationColorRed;
}
else
{
annView.pinColor = MKPinAnnotationColorOrange;
}
I'm trying to make parallax photo effect in my app using CSStickyHeaderFlowLayout
- https://github.com/jamztang/CSStickyHeaderFlowLayout/
and Swift language.
I have installed necessary Pod's file, add #import "CSStickyHeaderFlowLayout.h" to my "appName"-Bridging-Header.h file, and implemented this method:
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
var cell: UICollectionViewCell = UICollectionViewCell(frame: CGRectMake(0, 0, 400, 200));
var imageView: UIImageView = UIImageView();
var image: UIImage = UIImage(named: "news2.jpg")!;
imageView.image = image;
cell.addSubview(imageView);
return cell;
}
but now I have difficulties to translate this Obj-C code to Swift:
CSStickyHeaderFlowLayout *layout = (id)self.collectionViewLayout;
if ([layout isKindOfClass:[CSStickyHeaderFlowLayout class]]) {
layout.parallaxHeaderReferenceSize = CGSizeMake(320, 200);
}
In my opinion after that step my effect should works. Am I right? Or I have missed something?
Thanks for help,
m.af
You should be able to do it like this in Swift
if let layout = self.collectionViewLayout as? CSStickyHeaderFlowLayout {
layout.parallaxHeaderReferenceSize = CGSizeMake(320, 200)
}
You could try this:
var layout = CSStickyHeaderFlowLayout()
if (layout.isKindOfClass(CSStickyHeaderFlowLayout.self)) {
layout.parallaxHeaderReferenceSize = CGSizeMake(self.view.frame.size.width, 426)
layout.parallaxHeaderMinimumReferenceSize = CGSizeMake(self.view.frame.size.width, 110)
layout.itemSize = CGSizeMake(self.view.frame.size.width, layout.itemSize.height)
layout.parallaxHeaderAlwaysOnTop = true
// If we want to disable the sticky header effect
layout.disableStickyHeaders = true
}
As everyone know the UINavigationController push a ViewController from Left To Right, is there a way to push the View from Right To Left? like the animation for the back button.
For now I have this:
[self.navigationController pushViewController:viewController animated:YES];
You can create a NSMutableArray from the navigationController's array of viewcontrollers and insert new viewController before the current one. Then set the viewControllers array without animation and pop back.
UIViewController *newVC = ...;
NSMutableArray *vcs = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
[vcs insertObject:newVC atIndex:[vcs count]-1];
[self.navigationController setViewControllers:vcs animated:NO];
[self.navigationController popViewControllerAnimated:YES];
Please try this one
HomePageController *pageView = [[HomePageController alloc] initWithNibName:#"HomePageController_iPhone" bundle:nil];
CATransition *transition = [CATransition animation];
transition.duration = 0.45;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
transition.delegate = self;
[self.navigationController.view.layer addAnimation:transition forKey:nil];
self.navigationController.navigationBarHidden = NO;
[self.navigationController pushViewController:pageView animated:NO];
Try this :
//Push effect in reverse way
CATransition* transition = [CATransition animation];
transition.duration = 0.75;
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
[self.navigationController.view.layer addAnimation:transition forKey:kCATransition];
[self.navigationController pushViewController:vc animated:NO];
This code is working fine. Please try
CATransition *transition = [CATransition animation];
transition.duration = 0.3f;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionReveal;
[self.navigationController.view.layer addAnimation:transition forKey:nil];
[self.navigationController pushViewController:phoneServicesViewController animated:NO];
You seem to want to "pop back". This can be achieved by three methods on your UINavigationController instance:
To come back to the "root" controller:
-(NSArray *)popToRootViewControllerAnimated:(BOOL)animated
Or to come back to one of the previous controllers :
-(NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated
Or to come back to the previously pushed controller :
-(UIViewController *)popViewControllerAnimated:(BOOL)animated
No, right to left is reserved for popping viewcontrollers from the navigation stack. You can however get such an effect by animating the views yourself. Something like:
[UIView beginAnimations:#"rightToLeft" context:NULL];
CGRect newFrame = aView.frame;
newFrame.origin.x -= newFrame.size.width;
aView.frame = newFrame;
[UIView commitAnimations];
This will however not do anything to your navigation controller.
Just an improvement of the best answer here in my opinion:
UIViewController *newVC = ...;
[self.navigationController setViewControllers:#[newVC, self] animated:NO];
[self.navigationController popViewControllerAnimated:YES];
And to go back to the initial controller, just do a push to it.
Based on #felixlam 's answer, I upgraded and created a "reverse" direction navigation controller that overrides the default push / pop methods to act like this.
class LeftPushNavigationController: BaseNavigationController {
override func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.isEnabled = false
}
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
// If the push is not animated, simply pass to parent
guard animated else { return super.pushViewController(viewController, animated: animated) }
// Setup back button
do {
// Hide original back button
viewController.navigationItem.setHidesBackButton(true, animated: false)
// Add new one
viewController.navigationItem.rightBarButtonItem = UIBarButtonItem(
image: #imageLiteral(resourceName: "iconBackReverse"),
style: .plain,
target: self,
action: #selector(self.pop)
)
}
// Calculate final order
let finalVCs = self.viewControllers + [viewController]
// Insert the viewController to the before-last position (so it can be popped to)
var viewControllers = self.viewControllers
viewControllers.insert(viewController, at: viewControllers.count - 1)
// Insert viewcontroller before the last one without animation
super.setViewControllers(viewControllers, animated: false)
// Pop with the animation
super.popViewController(animated: animated)
// Set the right order without animation
super.setViewControllers(finalVCs, animated: false)
}
override func popViewController(animated: Bool) -> UIViewController? {
// If the push is not animated, simply pass to parent
guard animated else { return super.popViewController(animated: animated) }
guard self.viewControllers.count > 1 else { return nil }
// Calculate final order
var finalVCs = self.viewControllers
let viewController = finalVCs.removeLast()
// Remove the parent ViewController (so it can be pushed)
var viewControllers = self.viewControllers
let parentVC = viewControllers.remove(at: viewControllers.count - 2)
// Set the viewcontrollers without the parent & without animation
super.setViewControllers(viewControllers, animated: false)
// Create push animation with the parent
super.pushViewController(parentVC, animated: animated)
// Set the right final order without animation
super.setViewControllers(finalVCs, animated: false)
// Return removed viewController
return viewController
}
#objc
private func pop() {
_ = popViewController(animated: true)
}
}
Swift 5.0
let pageView = TaskFolderListViewController.init(nibName: "HomePageController_iPhone", bundle: nil)
let transition = CATransition()
transition.duration = 0.45
transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.default)
transition.type = CATransitionType.push
transition.subtype = CATransitionSubtype.fromLeft
self.navigationController?.view.layer.add(transition, forKey: kCATransition)
self.navigationController?.pushViewController(pageView, animated: false)
I had the same problem, this is how I solved it
[self.navigationController popViewControllerAnimated:YES];
This will resemble "going back" i.e a situation where a back button is clicked and you navigate to the previous page
Based on previous best answers, you can make UINavigationController extension and preserve the navigation stack as follows:
extension UINavigationController {
/// Pushes view controller into navigation stack with backwards animation.
func pushBackwards(viewController newViewController: UIViewController) {
if let currentController = viewControllers.last {
let previousControllers = viewControllers[0..<viewControllers.endIndex-1]
var controllers = Array(previousControllers + [newViewController, currentController])
setViewControllers(controllers, animated: false)
popViewController(animated: true)
// Adjusting the navigation stack
_ = controllers.popLast()
controllers.insert(currentController, at: controllers.count-1)
setViewControllers(controllers, animated: false)
} else {
// If there is no root view controller, set current one without animation.
setViewControllers([newViewController], animated: false)
}
}
}
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
}
}
Ive seen this effect in 2 apps and I REALLY want to find how to do it.
The animation is in a UIBarButtonItem, and is only to the image. The image is a + symbol, and it rotates to a X.
If you want to see the effect you have to start a conversation with someone and next to the text input theres the + button for images and emoji's. Or heres a video of the effect in another app, after he taps the bar button you see it rotate to a X, http://www.youtube.com/watch?v=S8JW7euuNMo.
I have found out how to do the effect but only on a UIImageView, I have to turn off all the autoresizing and the view mode has to be centered, then apply the rotation transform to it. I have tried many ways of trying to have it work in a bar item and so far the best way is adding a image view instance, then setting it up and setting the view mode centered and autoresizing off and then using that image view for a custom bar item view. But when i do this, the effect works except while its doing it, the image will go off to the side a little bit instead of staying where it already is. Ive tried getting the center before the animation and set it during the animation but that doesnt do anything.
So the answer for this is you have to make a instance of the Image view, then set it up with no resizing and view mode is centered. Then add the image view to a UIButton with custom type, and then use the button as the custom view for the bar item.
- (IBAction)animate {
[UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
imageView.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(45));
} completion:^(BOOL finished) {
imageView.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(0));
if ([imageView.image isEqual:[UIImage imageNamed:#"Add.png"]]) {
imageView.image = [UIImage imageNamed:#"Close.png"];
}
else imageView.image = [UIImage imageNamed:#"Add.png"];
}];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Add.png"]];
imageView.autoresizingMask = UIViewAutoresizingNone;
imageView.contentMode = UIViewContentModeCenter;
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(0, 0, 40, 40);
[button addSubview:imageView];
[button addTarget:self action:#selector(animate) forControlEvents:UIControlEventTouchUpInside];
imageView.center = button.center;
barItem = [[UIBarButtonItem alloc] initWithCustomView:button];
navItem.rightBarButtonItem = barItem;
}
Recently had to do the same thing in Swift. I created a tutorial that includes starter and final projects, and goes step-by-step with some tips sprinkled in. The code looks like this:
#IBOutlet weak var rightBarButton: UIBarButtonItem! {
didSet {
let icon = UIImage(named: "star")
let iconSize = CGRect(origin: CGPointZero, size: icon!.size)
let iconButton = UIButton(frame: iconSize)
iconButton.setBackgroundImage(icon, forState: .Normal)
rightBarButton.customView = iconButton
rightBarButton.customView!.transform = CGAffineTransformMakeScale(0, 0)
UIView.animateWithDuration(1.0,
delay: 0.5,
usingSpringWithDamping: 0.5,
initialSpringVelocity: 10,
options: .CurveLinear,
animations: {
self.rightBarButton.customView!.transform = CGAffineTransformIdentity
},
completion: nil
)
iconButton.addTarget(self, action: "tappedRightButton", forControlEvents: .TouchUpInside)
}
}
func tappedRightButton(){
rightBarButton.customView!.transform = CGAffineTransformMakeRotation(CGFloat(M_PI * 6/5))
UIView.animateWithDuration(1.0) {
self.rightBarButton.customView!.transform = CGAffineTransformIdentity
}
}
I wanted to keep the expanded tapping size that the native UIBarButtonItem view provides (such as -initWithBarButtonSystemItem:target:action: versus -initWithCustomView:).
Here's a basic implementation of my code.
- (void)setup {
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(navigationBarRightAction)];
}
- (void)navigationBarRightAction {
UIView *itemView = [self.navigationItem.rightBarButtonItem performSelector:#selector(view)];
UIImageView *imageView = [itemView.subviews firstObject];
if (self.shouldRotate) {
imageView.contentMode = UIViewContentModeCenter;
imageView.autoresizingMask = UIViewAutoresizingNone;
imageView.clipsToBounds = NO;
imageView.transform = CGAffineTransformMakeRotation(M_PI_4);
} else {
imageView.transform = CGAffineTransformIdentity;
}
}
You don't have to use a button as a custom view, it works in fact with less code using a UIImageView and adding a UITapGestureRecognizer.
I hope my solution below helps someone b/c I struggled with this for a long time until I got the bar button item to receive taps and get it to work with all the features I wanted. In my case, I made an "alert bell" bar button item that jingles when there are notifications, and then segues to a new tableview controller when tapped.
This was my solution (Swift 5):
#IBOutlet weak var notifyBell: UIBarButtonItem!
func updateNumNotesAndAnimateBell(_ numNotes: Int) {
guard let image = UIImage(named: "alertBellFill_\(numNotes)") else { return }
let imageView = UIImageView(image: image)
notifyBell.customView = imageView
notifyBell.customView?.contentMode = .center
let tap = UITapGestureRecognizer(target: self, action: #selector(notifyBellPressed))
notifyBell.customView?.addGestureRecognizer(tap)
let scaleTransformA = CGAffineTransform(scaleX: 0.8, y: 0.8)
let rotateTransformA = CGAffineTransform(rotationAngle: 0.0)
let hybridTransformA = scaleTransformA.concatenating(rotateTransformA)
let rotateTransformB = CGAffineTransform(rotationAngle: -1*CGFloat.pi*20.0/180.0)
let hybridTransformB = scaleTransformA.concatenating(rotateTransformB)
notifyBell.customView?.transform = hybridTransformA
UIView.animate(withDuration: 3,
delay: 1,
usingSpringWithDamping: 0.1,
initialSpringVelocity: 10,
options: [.allowUserInteraction, .curveEaseInOut],
animations: {
self.notifyBell.customView?.transform = numNotes > 0 ? hybridTransformB : scaleTransformA
},
completion: nil
)
}
#objc func notifyBellPressed(_ sender: UIBarButtonItem) {
performSegue(withIdentifier: "goToNotificationsTVC", sender: self)
}
Key discoveries for me were that:
-- .allowUserInteraction must be included in the animate options, otherwise the UIBarButtonItem won't be active until the animation completes.
-- You will likely have to declare YourBarButtonItem.customView?.contentMode = .center when using CGAffineTransform(rotationAngle: ) or else it will distort your image when it tries to rotate.
-- The code above includes a scale animation and rotate animation that is different depending on how many notifications I have. With zero notifications, the image is an empty bell, else, it displays the number of notifications in the bell image. I probably could've done this with an updating label, but I had already gone the route of making separate PNGs for each so this worked nicely.