I have a custom drawing view in iOS. It's in Xamarin, but I do not think that is relevant.
I get a touch begin event, a couple of move events, then the touch is cancelled. Specifically if I move my finger a bit away from the original touch point. If I stay within a couple of mm from the original touch point, it is not cancelled.
The code worked in iOS 10, but now it has stopped working. I suspect this is due to some new feature in iOS 11, but I can't figure it out.
The view is not within a ScrollView of any kind, just a plain UIViewController.
Does anyone know of any SDK changes in iOS 11 doing this to my draw view?
[Register("SignatureDrawView")]
public class SignatureDrawView : UIView
{
private UIImageView drawImage;
private List<CGPoint> currentLine;
public SignatureDrawView()
{
InitializeDrawImage();
}
public SignatureDrawView(NSCoder coder) : base(coder)
{
InitializeDrawImage();
}
public SignatureDrawView(NSObjectFlag t) : base(t)
{
InitializeDrawImage();
}
public SignatureDrawView(IntPtr handle) : base(handle)
{
InitializeDrawImage();
}
public SignatureDrawView(CGRect frame) : base(frame)
{
InitializeDrawImage();
}
private void InitializeDrawImage()
{
Layer.BorderColor = UIColor.Black.CGColor;
Layer.BorderWidth = 0.5f;
drawImage = new UIImageView
{
BackgroundColor = UIColor.Red,
Image = new UIImage()
};
AddSubview(drawImage);
}
public override void LayoutSubviews()
{
base.LayoutSubviews();
drawImage.Frame = new CGRect(0, 0, Frame.Size.Width, Frame.Size.Height);
}
public override void TouchesBegan(NSSet touches, UIEvent evt)
{
UITouch touch = (UITouch)touches.AnyObject;
CGPoint point = touch.LocationInView(this);
currentLine = new List<CGPoint>();
currentLine.Add(point);
SetNeedsDisplay();
}
public override void TouchesMoved(NSSet touches, UIEvent evt)
{
UITouch touch = (UITouch)touches.AnyObject;
CGPoint point = touch.LocationInView(this);
currentLine.Add(point);
SetNeedsDisplay();
}
public override void TouchesCancelled(NSSet touches, UIEvent evt)
{
SetNeedsDisplay();
}
public override void Draw(CGRect rect)
{
UIGraphics.BeginImageContext(drawImage.Frame.Size);
drawImage.Image.DrawAsPatternInRect(new CGRect(0, 0, drawImage.Frame.Size.Width, drawImage.Frame.Size.Height));
UIColor.Black.SetColor();
if (currentLine != null && currentLine.Count > 3)
{
UIBezierPath path = new UIBezierPath();
path.LineWidth = 1f;
for (int i = 3; i < currentLine.Count; i++)
{
CGPoint p0 = currentLine[i - 3];
CGPoint p1 = currentLine[i - 2];
CGPoint p2 = currentLine[i - 1];
CGPoint p3 = currentLine[i];
path.MoveTo(p0);
path.AddCurveToPoint(p3, p1, p2);
}
currentLine.RemoveRange(0, 3);
path.Stroke();
}
drawImage.Image = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
}
}
I found the answer. Throughout my app I use a side drawer, something like this: https://github.com/jdehlin/Xamarin-Sidebar.
The sidebar lib sets up a pan gesture for the drawer, and thet gesture recognizes probably cancelled my touch event. After disabling the drawer in that specific view, everything works as it shoud. I guess this is valid for normal iOS as well, not just Xamarin.
Related
I built a custom scrollview renderer on android which fades out my text on the bottom.
how do i do the same for iOS? I cant figure it out.
I know how to implement the renderer, but not how to make it fade
I added an empty FadeScrollView class to my main xamarin project.
public class FadeScrollView : ScrollView
{
}
I then load this scrollview renderer in my xaml page.
<ContentPage ...
xmlns:customRenders="clr-namespace:MyNameSpace;assembly=MyNameSpace"
>
...
<customRenderers:FadeScrollView>
....
</FadeScrollView>
</ContentPage>
I then set up the following custom renderer for android
[assembly: ExportRenderer(typeof(FadeScrollView), typeof(FadeScrollViewRendererAndroid))]
namespace MyNameSpace
{
public class FadeScrollViewRendererAndroid : ScrollViewRenderer
{
public FadeScrollViewRendererAndroid(Context context) : base(context)
{
this.SetFadingEdgeLength(300);
this.VerticalFadingEdgeEnabled = true;
}
}
}
And this results in an expected outcome:
Now I set up my iOS renderer in a similar way, but I'm not sure how to pull off the fade effect, and when I googled around I only found people showing you how to do it in swift
[assembly: ExportRenderer(typeof(FadeScrollView), typeof(FadeScrollViewRendererIOS))]
namespace MyNameSpace
{
class FadeScrollViewRendererIOS : UIScrollView
{
public FadeScrollViewRendererIOS()
{
//fade out
}
}
}
It seems iOS does not provide the api like VerticalFadingEdgeEnabled in Android .
We can implement this in custom renderer with a gradient layer .
Try the code below .
[assembly: ExportRenderer(typeof(ScrollView), typeof(MyRenderer))]
namespace FormsApp.iOS
{
class MyRenderer : ScrollViewRenderer
{
public override void LayoutSubviews()
{
base.LayoutSubviews();
gradient.Frame = this.Bounds;
}
CAGradientLayer gradient = new CAGradientLayer();
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if(e.NewElement != null)
{
gradient.Frame = this.Bounds;
gradient.Colors = new CGColor[] {UIColor.Clear.CGColor, UIColor.Black.CGColor,
UIColor.Black.CGColor, UIColor.Clear.CGColor };
gradient.Locations = new NSNumber[] {0.0,0.0,0.7,1 };
Layer.Mask = gradient;
this.Scrolled += (sender,ee)=> {
gradient.Frame = new CGRect(0, ContentOffset.Y, Bounds.Width, Bounds.Height);
if (ContentOffset.Y >= ContentSize.Height - Bounds.Height)
{
Layer.Mask = null;
}
else
{
Layer.Mask = gradient;
}
};
}
}
}
}
I'm trying to add gradient for TableViewCell. I'm updating the gradient layer in LayoutSubview(). When I debug it looks like it sets correct value for layer frame. But the gradient is not "redrawn" or something as on each rotation the gradient is displayed with size for previous orientation. What I'm doing wrong?
public override void AwakeFromNib()
{
base.AwakeFromNib();
_gradient = new CAGradientLayer();
_gradient.Frame = ContentView.Bounds;
_gradient.BackgroundColor = UIColor.Red.CGColor;
_gradient.StartPoint = new CGPoint(0, 0);
_gradient.EndPoint = new CGPoint(1, 1);
_gradient.Colors = new[] { _topColor, _bottomColor };
this.ContentView.Layer.InsertSublayer(_gradient, 0);
}
public override void LayoutSubviews()
{
_gradient.Frame = ContentView.Bounds;
base.LayoutSubviews();
}
I moved layer update from LayoutSubview to Draw as suggested by #dip
public override void Draw(CGRect rect)
{
_gradient.Frame = ContentView.Bounds;
base.Draw(rect);
}
The layout is working now.
I have created a View in Xamarin with a backing class and was wondering if I can create an instance of that class with a CGRect size. I have the following code right now:
using System;
using CoreGraphics;
using Foundation;
using UIKit;
namespace SimpleScroll.iOS
{
public partial class ViewController : UIViewController
{
public ViewController(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Perform any additional setup after loading the view, typically from a nib.
// UIScrollView with same width and height as ViewController
var mainScrollView = new UIScrollView(new CGRect(0, 0, this.View.Frame.Size.Width, this.View.Frame.Size.Height));
// Enable pagination and set other attributes
mainScrollView.PagingEnabled = true;
mainScrollView.ShowsVerticalScrollIndicator = false;
mainScrollView.ShowsHorizontalScrollIndicator = false;
mainScrollView.Bounces = false;
int numberOfViews = 2;
for (int i = 0; i < numberOfViews; i++)
{
nfloat xOrigin = i * this.View.Frame.Size.Width;
var subView = new UIView(new CGRect(xOrigin, 0, this.View.Frame.Size.Width, this.View.Frame.Size.Height));
subView.BackgroundColor = UIColor.FromRGBA(0.5f / i, 0.5f, 0.5f, 1);
mainScrollView.AddSubview(subView);
}
mainScrollView.ContentSize = new CGSize(this.View.Frame.Size.Width * numberOfViews, this.View.Frame.Size.Height);
this.View.AddSubview(mainScrollView);
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}
}
}
Instead of var subView = new UIView(new CGRect(xOrigin, 0, this.View.Frame.Size.Width, this.View.Frame.Size.Height));, would I be able to do the same thing with my own Menu class that I have created? How could I give Menu its own constructor so that I can do something along the lines of, var subView = new Menu(new CGRect(...));?
Just subclass uiview and override the frame constuctor
[Foundation.Register("Menu")]
public class Menu : UIView
{
public Menu(CGRect frame) : base(frame)
{
}
}
I have slide menu when I click on screen menu comes out from right to left. How can I write code if I click button to move back from left to right?
My slide menu:
My code:
private void UpdateTableViewPosition(CGPoint positon)
{
// set the position of the button based on the provided argument
_buttonToggleVisibility.Frame = new CoreGraphics.CGRect(positon.X , _buttonToggleVisibility.Frame.Y, _buttonToggleVisibility.Frame.Width, _buttonToggleVisibility.Frame.Height);
// TODO:
// if button is outside of the screen
// move it back into the screen
// then move tableview so it is right aligned of the button
_tableView.Frame = new CoreGraphics.CGRect(_buttonToggleVisibility.Frame.Right, _tableView.Frame.Y, _tableView.Frame.Width, _tableView.Frame.Height);
}
public override void TouchesEnded(NSSet touches, UIEvent evt)
{
base.TouchesEnded(touches, evt);
UITouch touch = (UITouch)touches.AnyObject;
_moving = false;
// TODO: fix so that only the button is clickable
// if the touch clicked the button
// open (or close) the view with the following animation code
UIView.Animate(0.9f, () =>
{
// TODO: this animation code is incorrect. Currently it moves the view 100px relatively to the
// current position, but it should rather either set the button to the left corner or the right
// corner at fixed positions
UpdateTableViewPosition(new CGPoint(_buttonToggleVisibility.Frame.X - _buttonToggleVisibility.Frame.Left, 0));
}, null);
}
public override void TouchesCancelled(NSSet touches, UIEvent evt)
{
base.TouchesCancelled(touches, evt);
_moving = false;
}
public override void TouchesBegan(Foundation.NSSet touches, UIEvent evt)
{
base.TouchesBegan(touches, evt);
UITouch touch = (UITouch)touches.AnyObject;
CoreGraphics.CGPoint pos = touch.LocationInView(this);
if (pos.X > _buttonToggleVisibility.Frame.X && pos.X < _buttonToggleVisibility.Frame.Right && pos.Y > _buttonToggleVisibility.Frame.Y && pos.Y < _buttonToggleVisibility.Frame.Bottom)
{
// did click on the view
_moving = true;
}
_buttonToggleVisibility.TouchUpInside += (sender, e) =>
{
};
}
}
I use AutoLayout and UIView animation to move a view of controls up and down. (Think a keyboard but with more flexibility.) The board has a "Done" button, which animatedly moves the board (and it's subviews) out of view.
Since I have an array of boards, there's extra code here addressing the tag property. That's where I find which board to work with.
The key things are to:
(1) Change the heightAnchor (or in your case the widthAnchor)
(2) Animate by call the superview's layoutIfNeeded
..
private var boardIn:[NSLayoutConstraint] = []
private var boardOut:[NSLayoutConstraint] = []
private var tabBoards:[TabBoard] = []
public func createControlBoard(
_ tag:Int
) -> ControlBoard {
let newBoard = ControlBoard(tag)
boardOut.append(newBoard.heightAnchor.constraint(equalToConstant: 100))
boardIn.append(newBoard.heightAnchor.constraint(equalToConstant: 0))
newBoard.doneButton.addTarget(self, action: #selector(hideTabBoard), for: .touchUpInside)
tabBoards.append(newBoard)
return newBoard
}
public func showTabBoard(_ tag:Int) {
for i in 0...tabBoards.count-1 {
NSLayoutConstraint.deactivate([boardOut[i]])
NSLayoutConstraint.activate([boardIn[i]])
tabBoards[i].makeControlsHidden(true)
}
NSLayoutConstraint.deactivate([boardIn[tag-1]])
NSLayoutConstraint.activate([boardOut[tag-1]])
tabBoards[tag-1].makeControlsHidden(false)
UIView.animate(withDuration: 0.3) { self.superview?.layoutIfNeeded() }
}
public func hideTabBoard(_ tag:Int) {
NSLayoutConstraint.deactivate([boardOut[tag-1]])
NSLayoutConstraint.activate([boardIn[tag-1]])
tabBoards[tag-1].makeControlsHidden(true)
UIView.animate(withDuration: 0.3) { self.superview?.layoutIfNeeded() }
}
I have a method to move a CollectionView if two text fields inside it are obscured by the frame of the iPad keyboard:
private void OnKeyboardNotification(NSNotification notification)
{
var activeTextField = FindFirstResponder(CollectionView);
NSDictionary userInfo = notification.UserInfo;
CGSize keyboardSize = ((NSValue)userInfo[UIKeyboard.FrameBeginUserInfoKey]).RectangleFValue.Size;
var contentInset = new UIEdgeInsets(0, 0, keyboardSize.Height, 0);
CollectionView.ContentInset = contentInset;
CollectionView.ScrollIndicatorInsets = contentInset;
CGRect oldRect = CollectionView.Frame;
CGRect aRect = new CGRect(oldRect.X, oldRect.Y, oldRect.Width,
oldRect.Height -= keyboardSize.Height);
if (!aRect.Contains(activeTextField.Frame.Location))
{
CGPoint scrollPoint = new CGPoint(0, activeTextField.Frame.Location.Y - (keyboardSize.Height - 15));
CollectionView.SetContentOffset(scrollPoint, true);
}
}
I don't believe the code is working as intended. It's complicated by the fact that I have a custom layout defined in a subclass of UICollectionViewFlowLayout. The layout allows me to have cells scrolling vertically which snap into focus.
Every time I call OnKeyboardNotification, override UICollectionViewLayoutAttributes[] is called afterwards in the custom layout. I thought this might be cancelling out the effect of the method, but if that's the case, then how can I change when UICollectionViewLayoutAttributes[] is called?
public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect(CGRect rect)
{
var array = base.LayoutAttributesForElementsInRect(rect);
var visibleRect = new CGRect(CollectionView.ContentOffset, CollectionView.Bounds.Size);
foreach (var attributes in array)
{
if (attributes.Frame.IntersectsWith(rect))
{
float distance = (float)(visibleRect.GetMidX() - attributes.Center.X);
float normalizedDistance = distance / ACTIVE_DISTANCE;
if (Math.Abs(distance) < ACTIVE_DISTANCE)
{
float zoom = 1 + ZOOM_FACTOR * (1 - Math.Abs(normalizedDistance));
attributes.Transform3D = CATransform3D.MakeScale(zoom, zoom, 1.0f);
attributes.ZIndex = 1;
}
}
}
return array;
}
Edit:
Here is an example of the problem.
I have two fields
and here, when 'edit mode' is entered, the keyboard hides the age field.
Seems like you use the sample from Xamarin Samples.
UICollectionViewLayoutAttributes will be invoked every time you change the size of your UICollectionView.
I suggest you changing the location of your view instead of changing size in your OnKeyboardNotification method:
CGRect oldRect = CollectionView.Frame;
CGRect aRect = new CGRect(oldRect.X, oldRect.Y - keyboardSize.Height, oldRect.Width,oldRect.Height);
Hope it can solve your problem, I'm midnight right now, if it can not work, leave some message, I will check latter when tomorrow morning.
Edit -----------------------------------------------------------------
I change some code in the Sample you using to make the effect.
At first, I choose the LineLayout in AppDelegate.cs, like this:
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
//Don't change anything
// simpleCollectionViewController = new SimpleCollectionViewController (flowLayout);
simpleCollectionViewController = new SimpleCollectionViewController (lineLayout);
// simpleCollectionViewController = new SimpleCollectionViewController (circleLayout);
simpleCollectionViewController.CollectionView.ContentInset = new UIEdgeInsets (50, 0, 0, 0);
window.RootViewController = simpleCollectionViewController;
window.MakeKeyAndVisible ();
return true;
}
And then, I add a static property to LineLayout.cs, like this:
public class LineLayout : UICollectionViewFlowLayout
{
private static bool flagForLayout = true;
public static bool FlagForLayout {
get {
return flagForLayout;
}
set {
flagForLayout = value;
}
}
public const float ITEM_SIZE = 200.0f;
public const int ACTIVE_DISTANCE = 200;
public const float ZOOM_FACTOR = 0.3f;
public LineLayout ()
{
//Don't change anything
}
public override bool ShouldInvalidateLayoutForBoundsChange (CGRect newBounds)
{
return flagForLayout;
}
public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect (CGRect rect)
{
//Don't change anything
}
public override CGPoint TargetContentOffset (CGPoint proposedContentOffset, CGPoint scrollingVelocity)
{
//Don't change anything
}
}
Then in the SimpleCollectionViewController.cs, I add a UITextField for every cell, like this:
public override UICollectionViewCell GetCell (UICollectionView collectionView, NSIndexPath indexPath)
{
var animalCell = (AnimalCell)collectionView.DequeueReusableCell (animalCellId, indexPath);
var animal = animals [indexPath.Row];
animalCell.Image = animal.Image;
animalCell.AddSubview (new UITextField (new CGRect (0, 0, 100, 30)){ BackgroundColor = UIColor.Red });
return animalCell;
}
And still in SimpleCollectionViewController.cs, I add some code in ViewDidload method and add two method to handle the keyboard display event, like this:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
CollectionView.RegisterClassForCell (typeof(AnimalCell), animalCellId);
CollectionView.RegisterClassForSupplementaryView (typeof(Header), UICollectionElementKindSection.Header, headerId);
UIMenuController.SharedMenuController.MenuItems = new UIMenuItem[] {
new UIMenuItem ("Custom", new Selector ("custom"))
};
this.View.AddGestureRecognizer(new UITapGestureRecognizer(()=>{
this.View.EndEditing(true);
}));
NSNotificationCenter.DefaultCenter.AddObserver(this,new ObjCRuntime.Selector("onKeyboardWillShowNotification:"),UIKeyboard.WillShowNotification,null);
NSNotificationCenter.DefaultCenter.AddObserver(this,new ObjCRuntime.Selector("onKeyboardWillHideNotification:"),UIKeyboard.WillHideNotification,null);
}
[Export("onKeyboardWillShowNotification:")]
private void OnKeyboardWillShowNotification(NSNotification notification)
{
LineLayout.FlagForLayout = false;
NSDictionary userInfo = notification.UserInfo;
CGSize keyboardSize = ((NSValue)userInfo[UIKeyboard.FrameBeginUserInfoKey]).RectangleFValue.Size;
CGRect oldRect = CollectionView.Frame;
CGRect aRect = new CGRect(oldRect.X, oldRect.Y - keyboardSize.Height, oldRect.Width,
oldRect.Height);
this.CollectionView.Frame = aRect;
}
[Export("onKeyboardWillHideNotification:")]
private void OnKeyboardWillHideNotification(NSNotification notification)
{
LineLayout.FlagForLayout = true;
this.CollectionView.Frame = UIScreen.MainScreen.Bounds;
}
Now LayoutAttributesForElementsInRect will be invoked when keyboard show, hope it can solve your problem.