Skip to content

Instantly share code, notes, and snippets.

@redent
Last active May 2, 2018 21:47
Show Gist options
  • Star 18 You must be signed in to star a gist
  • Fork 10 You must be signed in to fork a gist
  • Save redent/7263276 to your computer and use it in GitHub Desktop.
Save redent/7263276 to your computer and use it in GitHub Desktop.
Parent view controller to handle keyboard notifications to center views in the screen. UIScrollView related methods moved to an extension class.
using System;
using Foundation;
using UIKit;
using TwinCoders.TouchUtils.Extensions;
using CoreGraphics;
namespace SalesForce.Touch.Views
{
public abstract partial class ParentViewController
{
/// <summary>
/// Call this method from constructor, ViewDidLoad or ViewWillAppear to enable keyboard handling in the main partial class
/// </summary>
void InitKeyboardHandling()
{
//Only do this if required
if (HandlesKeyboardNotifications()) {
RegisterForKeyboardNotifications();
}
}
/// <summary>
/// Set this field to any view inside the textfield to center this view instead of the current responder
/// </summary>
protected UIView ViewToCenterOnKeyboardShown;
protected UIScrollView ScrollToCenterOnKeyboardShown;
/// <summary>
/// Override point for subclasses, return true if you want to handle keyboard notifications
/// to center the active responder in the scroll above the keyboard when it appears
/// </summary>
public virtual bool HandlesKeyboardNotifications() {
return false;
}
NSObject _keyboardShowObserver;
NSObject _keyboardHideObserver;
protected virtual void RegisterForKeyboardNotifications()
{
if (_keyboardShowObserver == null)
_keyboardShowObserver = NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillShowNotification, OnKeyboardNotification);
if (_keyboardHideObserver == null)
_keyboardHideObserver = NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillHideNotification, OnKeyboardNotification);
}
protected virtual void UnregisterForKeyboardNotifications()
{
if (_keyboardShowObserver != null)
{
NSNotificationCenter.DefaultCenter.RemoveObserver(_keyboardShowObserver);
_keyboardShowObserver.Dispose();
_keyboardShowObserver = null;
}
if (_keyboardHideObserver != null)
{
NSNotificationCenter.DefaultCenter.RemoveObserver(_keyboardHideObserver);
_keyboardHideObserver.Dispose();
_keyboardHideObserver = null;
}
}
/// <summary>
/// Gets the UIView that represents the "active" user input control (e.g. textfield, or button under a text field)
/// </summary>
/// <returns>
/// A <see cref="UIView"/>
/// </returns>
protected virtual UIView KeyboardGetActiveView()
{
return View.FindFirstResponder();
}
private void OnKeyboardNotification (NSNotification notification)
{
if (!IsViewLoaded) return;
//Check if the keyboard is becoming visible
var visible = notification.Name == UIKeyboard.WillShowNotification;
//Start an animation, using values from the keyboard
UIView.BeginAnimations ("AnimateForKeyboard");
UIView.SetAnimationBeginsFromCurrentState (true);
UIView.SetAnimationDuration (UIKeyboard.AnimationDurationFromNotification (notification));
UIView.SetAnimationCurve ((UIViewAnimationCurve)UIKeyboard.AnimationCurveFromNotification (notification));
//Pass the notification, calculating keyboard height, etc.
var keyboardFrame = visible
? UIKeyboard.FrameEndFromNotification(notification)
: UIKeyboard.FrameBeginFromNotification(notification);
OnKeyboardChanged (visible, keyboardFrame);
//Commit the animation
UIView.CommitAnimations ();
}
/// <summary>
/// Override this method to apply custom logic when the keyboard is shown/hidden
/// </summary>
/// <param name='visible'>
/// If the keyboard is visible
/// </param>
/// <param name='keyboardFrame'>
/// Frame of the keyboard
/// </param>
protected virtual void OnKeyboardChanged (bool visible, CGRect keyboardFrame)
{
var activeView = ViewToCenterOnKeyboardShown ?? KeyboardGetActiveView();
if (activeView == null)
return;
var scrollView = ScrollToCenterOnKeyboardShown ??
activeView.FindTopSuperviewOfType(View, typeof(UIScrollView)) as UIScrollView;
if (scrollView == null)
return;
if (!visible)
scrollView.RestoreScrollPosition();
else
scrollView.CenterView(activeView, keyboardFrame);
}
/// <summary>
/// Call it to force dismiss keyboard when background is tapped
/// </summary>
protected void DismissKeyboardOnBackgroundTap()
{
// Add gesture recognizer to hide keyboard
var tap = new UITapGestureRecognizer { CancelsTouchesInView = false };
tap.AddTarget(() => View.EndEditing(true));
tap.ShouldReceiveTouch = (recognizer, touch) =>
!(touch.View is UIControl || touch.View.FindSuperviewOfType(View, typeof(UITableViewCell)) != null);
View.AddGestureRecognizer(tap);
}
}
}
using System;
using UIKit;
using CoreGraphics;
namespace TwinCoders.TouchUtils.Extensions
{
public static class ScrollExtensions
{
public static void CenterView(this UIScrollView scrollView, UIView viewToCenter, CGRect keyboardFrame, bool animated = false) {
var scrollFrame = scrollView.Frame;
var adjustedFrame = UIApplication.SharedApplication.KeyWindow.ConvertRectFromView(scrollFrame, scrollView.Superview);
var intersect = CGRect.Intersect(adjustedFrame, keyboardFrame);
var height = intersect.Height;
if (!UIDevice.CurrentDevice.CheckSystemVersion(8, 0) && IsLandscape()) {
height = intersect.Width;
}
scrollView.CenterView(viewToCenter, height, animated:animated);
}
public static void CenterView(this UIScrollView scrollView, UIView viewToCenter, nfloat keyboardHeight = default(nfloat), bool adjustContentInsets = true, bool animated = false)
{
if (adjustContentInsets)
{
var contentInsets = new UIEdgeInsets(0.0f, 0.0f, keyboardHeight, 0.0f);
scrollView.ContentInset = contentInsets;
scrollView.ScrollIndicatorInsets = contentInsets;
}
// Position of the active field relative isnside the scroll view
CGRect relativeFrame = viewToCenter.Superview.ConvertRectToView(viewToCenter.Frame, scrollView);
var spaceAboveKeyboard = scrollView.Frame.Height - keyboardHeight;
// Move the active field to the center of the available space
var offset = relativeFrame.Y - (spaceAboveKeyboard - viewToCenter.Frame.Height) / 2;
if (scrollView.ContentOffset.Y < offset) {
scrollView.SetContentOffset(new CGPoint(0, offset), animated);
}
}
public static void RestoreScrollPosition(this UIScrollView scrollView)
{
scrollView.ContentInset = UIEdgeInsets.Zero;
scrollView.ScrollIndicatorInsets = UIEdgeInsets.Zero;
}
public static bool IsLandscape() {
var orientation = UIApplication.SharedApplication.StatusBarOrientation;
bool landscape = orientation == UIInterfaceOrientation.LandscapeLeft
|| orientation == UIInterfaceOrientation.LandscapeRight;
return landscape;
}
}
}
using System;
using UIKit;
using Foundation;
namespace TwinCoders.TouchUtils.Extensions
{
public static class ViewExtensions
{
/// <summary>
/// Find the first responder in the <paramref name="view"/>'s subview hierarchy
/// </summary>
/// <param name="view">
/// A <see cref="UIView"/>
/// </param>
/// <returns>
/// A <see cref="UIView"/> that is the first responder or null if there is no first responder
/// </returns>
public static UIView FindFirstResponder(this UIView view)
{
if (view.IsFirstResponder)
{
return view;
}
foreach (UIView subView in view.Subviews)
{
var firstResponder = subView.FindFirstResponder();
if (firstResponder != null)
return firstResponder;
}
return null;
}
/// <summary>
/// Find the first Superview of the specified type (or descendant of)
/// </summary>
/// <param name="view">
/// A <see cref="UIView"/>
/// </param>
/// <param name="stopAt">
/// A <see cref="UIView"/> that indicates where to stop looking up the superview hierarchy
/// </param>
/// <param name="type">
/// A <see cref="Type"/> to look for, this should be a UIView or descendant type
/// </param>
/// <returns>
/// A <see cref="UIView"/> if it is found, otherwise null
/// </returns>
public static UIView FindSuperviewOfType(this UIView view, UIView stopAt, Type type)
{
if (view.Superview != null)
{
if (type.IsInstanceOfType(view.Superview))
{
return view.Superview;
}
if (view.Superview != stopAt)
return view.Superview.FindSuperviewOfType(stopAt, type);
}
return null;
}
public static UIView FindTopSuperviewOfType(this UIView view, UIView stopAt, Type type)
{
var superview = view.FindSuperviewOfType(stopAt, type);
var topSuperView = superview;
while (superview != null && superview != stopAt)
{
superview = superview.FindSuperviewOfType(stopAt, type);
if (superview != null)
topSuperView = superview;
}
return topSuperView;
}
public static UIMotionEffect SetParallaxIntensity(this UIView view, float parallaxDepth, float? verticalDepth = null) {
if (UIDevice.CurrentDevice.CheckSystemVersion(7, 0))
{
float vertical = verticalDepth ?? parallaxDepth;
var verticalMotionEffect = new UIInterpolatingMotionEffect("center.y", UIInterpolatingMotionEffectType.TiltAlongVerticalAxis);
verticalMotionEffect.MinimumRelativeValue = new NSNumber(-vertical);
verticalMotionEffect.MaximumRelativeValue = new NSNumber(vertical);
var horizontalMotionEffect = new UIInterpolatingMotionEffect("center.x", UIInterpolatingMotionEffectType.TiltAlongHorizontalAxis);
horizontalMotionEffect.MinimumRelativeValue = new NSNumber(-parallaxDepth);
horizontalMotionEffect.MaximumRelativeValue = new NSNumber(parallaxDepth);
var group = new UIMotionEffectGroup();
group.MotionEffects = new UIMotionEffect[]{ horizontalMotionEffect, verticalMotionEffect };
view.AddMotionEffect(group);
return group;
}
return null;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment