TableViewCell with gradient layer -> layout issues - ios

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.

Related

How do I fade out the contents of a scrollview in Xamarin Forms iOS?

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;
}
};
}
}
}
}

View height constraints are not set correctly as I scroll UICollectionView up and down

I'm trying to decide view heights based on a model property, but as UICollectionView is scrolled up and down, incorrect heights are assigned to visible cells. It seems that setting a HeightAnchor in GetCell (i.e. cellForItemAtIndexPath) does not work. How can I make this work?
using CoreGraphics;
using Foundation;
using System;
using System.Collections.Generic;
using UIKit;
namespace App2
{
public partial class ViewController : UIViewController
{
private UICollectionView _collectionView;
public ViewController (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
InitializeCollectionView();
}
private void InitializeCollectionView()
{
_collectionView = new UICollectionView(View.Frame, new UICollectionViewCompositionalLayout(GetSection()))
{
DataSource = new CustomUICollectionViewDataSource(),
TranslatesAutoresizingMaskIntoConstraints = false
};
_collectionView.RegisterClassForCell(typeof(CustomUICollectionViewCell), "CustomUICollectionViewCell");
View.AddSubview(_collectionView);
NSLayoutConstraint.ActivateConstraints(new[]
{
_collectionView.TopAnchor.ConstraintEqualTo(View.SafeAreaLayoutGuide.TopAnchor),
_collectionView.BottomAnchor.ConstraintEqualTo(View.SafeAreaLayoutGuide.BottomAnchor),
_collectionView.LeftAnchor.ConstraintEqualTo(View.SafeAreaLayoutGuide.LeftAnchor),
_collectionView.RightAnchor.ConstraintEqualTo(View.SafeAreaLayoutGuide.RightAnchor)
});
}
private static NSCollectionLayoutSection GetSection()
{
var size = NSCollectionLayoutSize.Create(NSCollectionLayoutDimension.CreateFractionalWidth(1), NSCollectionLayoutDimension.CreateEstimated(50));
var item = NSCollectionLayoutItem.Create(size);
var group = NSCollectionLayoutGroup.CreateHorizontal(layoutSize: size, subitem: item, count: 1);
var section = NSCollectionLayoutSection.Create(group);
section.InterGroupSpacing = 5;
return section;
}
}
public class CustomUICollectionViewDataSource : UICollectionViewDataSource
{
private readonly List<Model> _models = new List<Model>
{
new Model {Height = 250},
new Model {Height = 100},
new Model {Height = 300},
new Model {Height = 400},
new Model {Height = 500},
new Model {Height = 50},
new Model {Height = 230},
new Model {Height = 100},
new Model {Height = 600},
new Model {Height = 310},
new Model {Height = 150},
new Model {Height = 220}
};
public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
{
var model = _models[(int)indexPath.Item];
var cell = collectionView.DequeueReusableCell("CustomUICollectionViewCell", indexPath) as CustomUICollectionViewCell;
cell.UpdateHeight(model.Height);
return cell;
}
public override nint GetItemsCount(UICollectionView collectionView, nint section)
{
return _models.Count;
}
}
public sealed class CustomUICollectionViewCell : UICollectionViewCell
{
private readonly UIView _uiView;
[Export("initWithFrame:")]
public CustomUICollectionViewCell(CGRect frame) : base(frame)
{
_uiView = new UIView
{
BackgroundColor = UIColor.Brown,
TranslatesAutoresizingMaskIntoConstraints = false
};
ContentView.AddSubview(_uiView);
NSLayoutConstraint.ActivateConstraints(new[]
{
_uiView.TopAnchor.ConstraintEqualTo(ContentView.SafeAreaLayoutGuide.TopAnchor),
_uiView.BottomAnchor.ConstraintEqualTo(ContentView.SafeAreaLayoutGuide.BottomAnchor),
_uiView.LeftAnchor.ConstraintEqualTo(ContentView.SafeAreaLayoutGuide.LeftAnchor),
_uiView.RightAnchor.ConstraintEqualTo(ContentView.SafeAreaLayoutGuide.RightAnchor)
});
}
public void UpdateHeight(int height)
{
_uiView.HeightAnchor.ConstraintEqualTo(height).Active = true;
}
}
public class Model
{
public int Height { get; set; }
}
}
If you do this, the print message should prompt you to repeat the constraint.
You set the constraints of left, right, bottom, top, and added a height constraint when updating. The first four constraints have already determined the final height, the new height here will not work, and a warning message will be printed.
If you really want to update the height, you should set the left, right, top, height constraints from the beginning, and save the height constraint, which is used when updating.
var heightConstraint: NSLayoutConstraint?
heightConstraint = _uiView.heightAnchor.constraint(equalToConstant: 50)//Defaults
NSLayoutConstraint.activate([
(_uiView.topAnchor.constraint(equalTo:ContentView.SafeAreaLayoutGuide.topAnchor))!,
(_uiView.leftAnchor.constraint(equalTo:ContentView.SafeAreaLayoutGuide.leftAnchor))!
(_uiView.rightAnchor.constraint(equalTo:ContentView.SafeAreaLayoutGuide.rightAnchor))!,
(heightConstraint)!
]);
public void UpdateHeight(int height){
heightConstraint?.isActive = false
heightConstraint = _uiView.heightAnchor.constraint(equalToConstant: height)
heightConstraint?.isActive = true
}
Here's the fix for this as recommended by Xamarin support:
NSLayoutConstraint heightConstraint;
public void UpdateHeight(int height)
{
if (heightConstraint == null)
{
heightConstraint = _uiView.HeightAnchor.ConstraintEqualTo(height);
heightConstraint.Active = true;
}
else
{
heightConstraint.Constant = height;
}
}

iOS 11 touch cancelled

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.

Creating Custom Sized Views Xamarin.iOS

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)
{
}
}

How can I move a view to see a UITextField when the keyboard is present, when using UICollectionViewFlowLayout

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.

Resources