What methods are called after DidSelectAnnotationView/DidDeSelectAnnotationView in MapView in Xamarin iOS? - ios
I'm working on a Xamarin iOS project and have a view with a MapView that contains modified MKAnnotations and implements IMKMapViewDelegate.
The view shows a map with a few annotations on the map. Tapping an annotation displays a callout bubble with extra info.
The view also has an overlay view at the bottom of the screen which can be swiped up to display detailed info. The problem is that when an annotation is selected or deselected, the overlay view moves half way up the screen. The annotation selection event calls the methods DidSelectAnnotationView/DidDeselectAnnotationView but the movement of the overlay is triggered after those calls and I cannot hit any break points in any of my code when this event occurs.
So the question is, which iOS methods get called after DidSelectAnnotationView and DidDeselectAnnotationView which could be causing my issue?
The code below reverts the overlay back to its original position but with an obvious UI glitch.
[Export("mapView:didSelectAnnotationView:")]
public void DidSelectAnnotationView(MKMapView mapView, MKAnnotationView annotationView)
{
DelayedOverlayReset();
}
[Export("mapView:didDeselectAnnotationView:")]
public void DidDeselectAnnotationView(MKMapView mapView, MKAnnotationView annotationView)
{
DelayedOverlayReset();
}
private async Task DelayedOverlayReset()
{
await Task.Run(async () =>
{
Thread.Sleep(150);
});
_overlayView?.SetUpOverlayMinPosition();
}
I can share more code but its work related so I need to anonymise it. Happy to provide further detail or answer questions as necessary.
EDIT overlay view:
namespace Touch.Views.Detail
{
[Register("DetailsOverlayView")]
public sealed class DetailsOverlayView : UIView
{
private const float OverlayDragBarWidth = 56.0f;
private const float OverlayDragBarTopPadding = 20.0f;
private const float OverlayDragBarHeight = 6.0f;
private const float OverlayDragBarBottomPadding = 18.0f;
private const float CornerRadius = 18.0f;
private const float ScoreImageWidth = 60.0f;
private const float IconImageWidth = 16.0f;
private const float ShadowOpacity = 0.15f;
private const float ShadowRadius = 10.0f;
private const float ShadowOffsetWidth = 0f;
private const float ShadowOffsetHeight = 10.0f;
private const float ViewAnchorMidPoint = 0.5f;
private const float ImageDurationToLabelGap = 30;
private const float AddressIconWidthAndPadding = 18.0f;
private const double StartingOverlayAnimationSpeed = 0.3;
private const double AnimationSpeedThreshold = 1.3;
private const string DistanceImageName = "icon_map";
private const string DurationImageName = "icon_clock";
private static readonly TextStyle Heading3SemiBold = TextStyle.Heading3.SemiBold.BrandSecondary;
private static readonly TextStyle BodyRegularCaption = TextStyle.Body.Regular.TextCaption;
private readonly DetailOverlayDisplayData? _displayData;
private readonly double _maxPosition;
private readonly double _minPosition;
private readonly UIView? _parentView;
private readonly ElementLinksViewModel? _ActionViewModel;
private NSLayoutConstraint? _dragBarBottomPadding;
private UIView? _overlayDragBarView;
private UIView? _overlayHeaderAndContentView;
private UIPanGestureRecognizer? _panGestureRecognizer;
private UIScrollView? _scrollView;
private UIStackView? _stackView;
private double _startingPosition;
public NSLayoutConstraint? OverlayHeight;
public DetailsOverlayView(DetailOverlayDisplayData displayData, ElementLinksViewModel action,
UIView parentView)
{
_parentView = parentView;
_displayData = displayData;
_ActionViewModel = action;
TranslatesAutoresizingMaskIntoConstraints = false;
BackgroundColor = UIColor.White;
ClipsToBounds = false;
Layer.CornerRadius = CornerRadius;
Hidden = true;
Layer.ShadowOpacity = ShadowOpacity;
Layer.ShadowOffset = new CGSize(ShadowOffsetWidth, -ShadowOffsetHeight);
Layer.ShadowRadius = ShadowRadius;
SetUp();
var headerView = new DetailHeaderView();
headerView.SetUp(displayData, _scrollView!, _stackView!, parentView);
CreateContentPanel();
SetUpOverlayConstraints();
_minPosition = Math.Round(parentView.Frame.Height - GetHeaderHeight(displayData, parentView.Bounds.Width));
_maxPosition = 0;
}
public void SetUpOverlayStartingPosition()
{
if (_parentView == null)
{
return;
}
Center = new CGPoint(Center.X, _parentView.Frame.Height);
Layer.AnchorPoint = new CGPoint(ViewAnchorMidPoint, 0f);
Hidden = false;
SetOverlayPosition(StartingOverlayAnimationSpeed, _minPosition, CornerRadius, true);
_overlayDragBarView!.Hidden = false;
}
public void SetUpOverlayMinPosition()
{
SetOverlayPosition(0.01, _minPosition, CornerRadius, true);
_overlayDragBarView!.Hidden = false;
}
private void SetUp()
{
_overlayDragBarView = new UIView
{
TranslatesAutoresizingMaskIntoConstraints = false,
BackgroundColor = UIColor.LightGray,
ClipsToBounds = true
};
_overlayDragBarView.RoundCorners(ViewCornerStyle.All, OverlayDragBarHeight / 2);
_overlayHeaderAndContentView = new UIView
{
TranslatesAutoresizingMaskIntoConstraints = false,
ClipsToBounds = true
};
AddSubview(_overlayDragBarView);
AddSubview(_overlayHeaderAndContentView);
_panGestureRecognizer = new UIPanGestureRecognizer(PanOverlayView);
_panGestureRecognizer.CancelsTouchesInView = true;
AddGestureRecognizer(_panGestureRecognizer);
var (outerScrollView, _, innerStackView) =
_overlayHeaderAndContentView!.CreateScrollViewWithFooterAndStackView(UIColor.White);
_stackView = innerStackView;
_stackView.LayoutMarginsRelativeArrangement = true;
_scrollView = outerScrollView;
_scrollView.UserInteractionEnabled = false;
_scrollView.Bounces = true;
_scrollView.SetContentOffset(CGPoint.Empty, true);
_scrollView.Scrolled += ScrollViewOnScrolled;
}
private void CreateContentPanel()
{
var view = new UIView
{
TranslatesAutoresizingMaskIntoConstraints = false,
BackgroundColor = UIColor.Clear,
ClipsToBounds = true
};
var contentStackView = new UIStackView
{
TranslatesAutoresizingMaskIntoConstraints = false,
Axis = UILayoutConstraintAxis.Vertical,
LayoutMarginsRelativeArrangement = true,
Spacing = StyleGuideDimens.PaddingSmall,
DirectionalLayoutMargins =
new NSDirectionalEdgeInsets(0, StyleGuideDimens.PaddingSmall, StyleGuideDimens.PaddingSmall,
StyleGuideDimens.PaddingSmall)
};
_stackView!.AddArrangedSubview(view);
view.AddSubview(contentStackView);
NSLayoutConstraint.ActivateConstraints(new[]
{
view.LeadingAnchor.ConstraintEqualTo(view.Superview.LeadingAnchor),
view.TrailingAnchor.ConstraintEqualTo(view.Superview.TrailingAnchor),
contentStackView.TopAnchor.ConstraintEqualTo(view.TopAnchor),
contentStackView.BottomAnchor.ConstraintEqualTo(view.BottomAnchor),
contentStackView.LeadingAnchor.ConstraintEqualTo(view.LeadingAnchor),
contentStackView.TrailingAnchor.ConstraintEqualTo(view.TrailingAnchor)
});
var milesTimePanel = SetUpDistanceAndDurationLayout();
var ActionView = ElementLinksView.Create();
_ActionViewModel!.Alignment = Alignment.Center;
ActionView.SetUp(_ActionViewModel);
var scoreSummaryView = new ScoreSummaryView();
scoreSummaryView.SetUp(_displayData!.ScoreSummary);
contentStackView.AddArrangedSubview(milesTimePanel);
contentStackView.AddArrangedSubview(ActionView);
contentStackView.AddArrangedSubview(scoreSummaryView);
contentStackView.SetCustomSpacing(StyleGuideDimens.PaddingMedium, milesTimePanel);
milesTimePanel.LayoutIfNeeded();
view.LayoutIfNeeded();
_stackView.LayoutIfNeeded();
}
private void SetUpOverlayConstraints()
{
NSLayoutConstraint.ActivateConstraints(new[]
{
_overlayDragBarView!.CenterXAnchor.ConstraintEqualTo(CenterXAnchor),
_overlayDragBarView.TopAnchor.ConstraintEqualTo(TopAnchor, OverlayDragBarTopPadding),
_overlayDragBarView.WidthAnchor.ConstraintEqualTo(OverlayDragBarWidth),
_overlayDragBarView.HeightAnchor.ConstraintEqualTo(OverlayDragBarHeight),
_dragBarBottomPadding = _overlayHeaderAndContentView!.TopAnchor.ConstraintEqualTo(
_overlayDragBarView!.BottomAnchor, OverlayDragBarBottomPadding),
_overlayHeaderAndContentView.BottomAnchor.ConstraintEqualTo(BottomAnchor),
_overlayHeaderAndContentView.LeadingAnchor.ConstraintEqualTo(LeadingAnchor),
_overlayHeaderAndContentView.TrailingAnchor.ConstraintEqualTo(TrailingAnchor)
});
_overlayDragBarView.LayoutIfNeeded();
_overlayHeaderAndContentView.LayoutIfNeeded();
}
private void PanOverlayView(UIPanGestureRecognizer gesture)
{
if (_parentView == null || OverlayHeight == null)
{
return;
}
if (gesture.State == UIGestureRecognizerState.Began)
{
_startingPosition = Frame.Y;
}
var scrollOffset = _scrollView!.ContentOffset.Y;
var velocity = gesture.VelocityInView(_parentView);
var y = _parentView.Frame.GetMinY();
var newPos = _startingPosition + gesture.TranslationInView(_parentView).Y;
if (newPos >= _minPosition)
{
SetOverlayPosition(0, _minPosition);
}
else if (newPos <= _maxPosition)
{
SetOverlayPosition(0, _maxPosition, 0, true, 0);
}
else
{
//Scroll the scrollView content to the top
_scrollView!.SetContentOffset(CGPoint.Empty, true);
if (scrollOffset <= 0)
{
Frame = new CGRect(0, newPos, Frame.Width, Frame.Height);
}
}
if (gesture.State == UIGestureRecognizerState.Ended)
{
var duration = velocity.Y < 0 ? (y - _maxPosition) / -velocity.Y : _minPosition - (y / velocity.Y);
duration = duration > AnimationSpeedThreshold ? 1 : duration;
if (velocity.Y > 0)
{
if (scrollOffset <= 0)
{
//Scroll the scrollView content to the top
_scrollView!.SetContentOffset(CGPoint.Empty, true);
_scrollView!.UserInteractionEnabled = false;
SetOverlayPosition(duration, _minPosition);
}
}
else
{
_scrollView!.UserInteractionEnabled = true;
SetOverlayPosition(duration, _maxPosition, 0, true, 0);
}
}
}
private void SetOverlayPosition(double animationDuration, double position, float cornerRadius = CornerRadius,
bool dragBarVisibility = false, float dragBarBottomPadding = 18.0f)
{
Animate(animationDuration, () =>
{
Frame = new CGRect(0, position, Frame.Width, Frame.Height);
_overlayDragBarView!.Hidden = dragBarVisibility;
_dragBarBottomPadding!.Constant = dragBarBottomPadding;
});
AnimateCornerRadius(cornerRadius);
}
private void ScrollViewOnScrolled(object sender, EventArgs e)
{
var scrollOffset = _scrollView!.ContentOffset.Y;
if (scrollOffset >= -1 && _panGestureRecognizer != null)
{
_scrollView.UserInteractionEnabled = false;
}
}
#region Helper Methods
private void AnimateCornerRadius(float cornerRadius) =>
Animate(0.1, () => { Layer.CornerRadius = cornerRadius; });
private UIView SetUpDistanceAndDurationLayout()
{
var containerView = new UIView
{
TranslatesAutoresizingMaskIntoConstraints = false,
ClipsToBounds = false
};
if (_displayData == null)
{
return containerView;
}
var imageDistance = new UIImageView
{
TranslatesAutoresizingMaskIntoConstraints = false,
Image = UIImage.FromBundle(DistanceImageName),
ContentMode = UIViewContentMode.ScaleAspectFit
};
var imageDuration = new UIImageView
{
TranslatesAutoresizingMaskIntoConstraints = false,
Image = UIImage.FromBundle(DurationImageName),
ContentMode = UIViewContentMode.ScaleAspectFit
};
var distanceDurationWrapperView = new UIView
{
TranslatesAutoresizingMaskIntoConstraints = false,
ClipsToBounds = true
};
var stackView = new UIStackView
{
TranslatesAutoresizingMaskIntoConstraints = false,
Axis = UILayoutConstraintAxis.Horizontal,
Alignment = UIStackViewAlignment.Fill,
Spacing = 0
};
containerView.AddSubview(stackView);
stackView.TopAnchor.ConstraintEqualTo(containerView.TopAnchor).Active = true;
stackView.BottomAnchor.ConstraintEqualTo(containerView.BottomAnchor).Active = true;
stackView.LeadingAnchor.ConstraintEqualTo(containerView.LeadingAnchor).Active = true;
stackView.TrailingAnchor.ConstraintEqualTo(containerView.TrailingAnchor, 0).Active = true;
var distanceLabel = _displayData.TotalMiles.CreateLabel(BodyRegularCaption, ElementMargins.None(),
UITextAlignment.Left);
var durationLabel = _displayData.TotalJourneyTime.CreateLabel(BodyRegularCaption, ElementMargins.None(),
UITextAlignment.Left);
stackView.AddArrangedSubview(distanceDurationWrapperView);
distanceDurationWrapperView.AddSubview(imageDistance);
distanceDurationWrapperView.AddSubview(distanceLabel);
distanceDurationWrapperView.AddSubview(imageDuration);
distanceDurationWrapperView.AddSubview(durationLabel);
imageDistance.WidthAnchor.ConstraintEqualTo(IconImageWidth).Active = true;
imageDistance.HeightAnchor.ConstraintEqualTo(IconImageWidth).Active = true;
imageDuration.WidthAnchor.ConstraintEqualTo(IconImageWidth).Active = true;
imageDuration.HeightAnchor.ConstraintEqualTo(IconImageWidth).Active = true;
imageDistance.CenterYAnchor.ConstraintEqualTo(distanceDurationWrapperView.CenterYAnchor).Active = true;
imageDistance.LeadingAnchor.ConstraintEqualTo(distanceDurationWrapperView.LeadingAnchor).Active = true;
distanceLabel.TopAnchor.ConstraintEqualTo(distanceDurationWrapperView.TopAnchor).Active = true;
distanceLabel.BottomAnchor.ConstraintEqualTo(distanceDurationWrapperView.BottomAnchor, 0).Active = true;
distanceLabel.LeadingAnchor.ConstraintEqualTo(imageDistance.TrailingAnchor,
StyleGuideDimens.PaddingExtraSmall).Active = true;
imageDuration.CenterYAnchor.ConstraintEqualTo(distanceDurationWrapperView.CenterYAnchor).Active = true;
imageDuration.LeadingAnchor.ConstraintEqualTo(distanceLabel.TrailingAnchor, ImageDurationToLabelGap)
.Active = true;
durationLabel.TopAnchor.ConstraintEqualTo(distanceDurationWrapperView.TopAnchor).Active = true;
durationLabel.BottomAnchor.ConstraintEqualTo(distanceDurationWrapperView.BottomAnchor, 0).Active = true;
durationLabel.LeadingAnchor.ConstraintEqualTo(imageDuration.TrailingAnchor,
StyleGuideDimens.PaddingExtraSmall).Active = true;
durationLabel.TrailingAnchor.ConstraintEqualTo(distanceDurationWrapperView.TrailingAnchor).Active = true;
distanceDurationWrapperView.LayoutIfNeeded();
containerView.LayoutIfNeeded();
stackView.LayoutIfNeeded();
return containerView;
}
public static nfloat GetHeaderHeight(JourneyDetailOverlayDisplayData displayData, nfloat availableWidth)
{
//Left padding, left score panel padding, score panel width, right padding
var totalAvailableWidth = availableWidth - (StyleGuideDimens.PaddingSmall +
StyleGuideDimens.PaddingSmall + ScoreImageWidth + StyleGuideDimens.PaddingSmall);
var headerString = new NSString(displayData.JourneyDate);
var distance = new NSString(displayData.TotalMiles);
var duration = new NSString(displayData.TotalJourneyTime);
var sizeOfHeader = headerString.GetCalculatedSizeOfString(FontStyle.SemiBold.GetFont(Heading3SemiBold.Size),
totalAvailableWidth);
var (sizeOfStartAddress, sizeOfEndAddress) = GetAddressPanelHeights(displayData, availableWidth);
var sizeOfDistance = distance.GetCalculatedSizeOfString(FontStyle.Regular.GetFont(BodyRegularCaption.Size),
totalAvailableWidth);
var sizeOfDuration = duration.GetCalculatedSizeOfString(FontStyle.Regular.GetFont(BodyRegularCaption.Size),
totalAvailableWidth);
var distanceHeight = sizeOfDistance.Height > IconImageWidth
? sizeOfDistance.Height
: IconImageWidth;
var durationHeight = sizeOfDuration.Height > IconImageWidth
? sizeOfDuration.Height
: IconImageWidth;
var distanceDurationPanelHeight = distanceHeight > durationHeight ? distanceHeight : durationHeight;
return OverlayDragBarTopPadding
+ OverlayDragBarHeight
+ OverlayDragBarBottomPadding
+ sizeOfHeader.Height
+ StyleGuideDimens.PaddingSmall
+ sizeOfStartAddress.Height
+ StyleGuideDimens.PaddingExtraSmall
+ sizeOfEndAddress.Height
+ StyleGuideDimens.PaddingMedium
+ distanceDurationPanelHeight
+ StyleGuideDimens.PaddingSmall;
}
public static (CGSize startAddressHeight, CGSize endAddressHeight) GetAddressPanelHeights(
JourneyDetailOverlayDisplayData displayData, nfloat availableWidth)
{
//Left padding, Address icon width and padding, left score panel padding, score panel width, right padding
var totalAvailableWidth = availableWidth - (StyleGuideDimens.PaddingSmall + AddressIconWidthAndPadding +
StyleGuideDimens.PaddingSmall + ScoreImageWidth + StyleGuideDimens.PaddingSmall);
var startAddress = new NSString(displayData.JourneyStartAddress);
var endAddress = new NSString(displayData.JourneyEndAddress);
var sizeOfStartAddress = startAddress.GetCalculatedSizeOfString(
FontStyle.Regular.GetFont(BodyRegularCaption.Size),
totalAvailableWidth);
var sizeOfEndAddress = endAddress.GetCalculatedSizeOfString(
FontStyle.Regular.GetFont(BodyRegularCaption.Size),
totalAvailableWidth);
return (sizeOfStartAddress, sizeOfEndAddress);
}
#endregion
}
}
Related
Find value selected by UIPickerView which I added as SubView inside UIAlertController
I have UITableViewController, inside that, I added a button in the Cell. Upon clicking the button, it shows a UIAlertController with "Ok" & "Cancel" Actions. In my UIAlertController, I added a UIPickerView with some options. When user clicks on the button in the cell, UIAlertController Popsup and shows the UIPickerView inside AlertBox with "OK" & "Cancel" actions. Can you please help me in finding the value Selected by UIPickerView upon clicking the "Ok" button of UIAlertController. namespace Navigation { public partial class ReviewInspectionViewController : UIViewController { private string filePath; private InspectionData inspData = null; List<ReportObservation> lstCriticalObs = new List<ReportObservation>(); List<ReportObservation> lstNonCriticalObs = new List<ReportObservation>(); public Audits Audit { get { return this._audit; } set { this._audit = value; } } public ReviewInspectionViewController(IntPtr handle) : base(handle) { } public override void ViewWillAppear(bool animated) { base.ViewWillAppear(animated); } public override void ViewDidLoad() { base.ViewDidLoad(); filePath = InProgressPath + Audit.AssessmentId.ToString() + ".xml"; inspData = ReadInspectionData(filePath); if (inspData != null) { for (int i = 0; i < inspData.ReportSections.Count; i++) { lstCriticalObs.AddRange(inspData.ReportSections[i].ReportObservations.FindAll(x => x.Critical == true)); } for (int i = 0; i < inspData.ReportSections.Count; i++) { lstNonCriticalObs.AddRange(inspData.ReportSections[i].ReportObservations.FindAll(x => x.Critical == false)); } tblReviewInspection.Source = new ReviewInspectionSource(this, inspData, lstCriticalObs, lstNonCriticalObs); tblReviewInspection.RowHeight = 160.0f; } } } class ReviewInspectionSource : UITableViewSource { NSString _cellID = new NSString("TableCell"); ReviewInspectionViewController _parent; private InspectionData _inspectionData; private List<ReportObservation> _lstCriticalObs; private List<ReportObservation> _lstNonCriticalObs; UITableView tvStatus; public ReviewInspectionSource(ReviewInspectionViewController parent, InspectionData inspectionData, List<ReportObservation> lstCriticalObs, List<ReportObservation> lstNonCriticalObs) { _inspectionData = inspectionData; _lstCriticalObs = lstCriticalObs; _lstNonCriticalObs = lstNonCriticalObs; _parent = parent; } public override nint NumberOfSections(UITableView tableView) { return _lstCriticalObs.Count; } public override nint RowsInSection(UITableView tableview, nint section) { int cnt = _lstCriticalObs[Convert.ToInt32(section)].ReportFindings.Count; return cnt; } public override UIView GetViewForHeader(UITableView tableView, nint section) { UIView ctrls = new UIView(); tvStatus = new UITableView(); UILabel headerLabel = new UILabel(new CGRect(55, 4, 350, 33)); // Set the frame size you need headerLabel.TextColor = UIColor.Red; // Set your color headerLabel.Text = _lstCriticalObs[Convert.ToInt32(section)].ObsTitle; UIButton btnEdit = new UIButton(new CGRect(5, 4, 50, 33)); var newFont = UIFont.SystemFontOfSize(17); btnEdit.SetTitle("Edit", UIControlState.Normal); btnEdit.Font = newFont; btnEdit.BackgroundColor = UIColor.Yellow; btnEdit.SetTitleColor(UIColor.Black, UIControlState.Normal); btnEdit.SetTitleColor(UIColor.Black, UIControlState.Selected); btnEdit.TouchUpInside += (object sender, EventArgs e) => { tvStatus.Frame = new CGRect(((UIButton)sender).Frame.X, ((UIButton)sender).Frame.Y, 50, 230); tvStatus.Hidden = false; btnEdit.Hidden = true; //string msg = "SectionIndex: " + section; var Alert = UIAlertController.Create("Inspection", "\n\n\n\n\n\n", UIAlertControllerStyle.Alert); Alert.ModalInPopover = true; var picker = new UIPickerView(new CGRect(5, 20, 250, 140)) ; picker.Model = new ObsStatusModel(); Alert.View.AddSubview(picker); Alert.AddTextField((field) => { field.Placeholder = "email address"; }); Alert.AddAction(UIAlertAction.Create("Ok", UIAlertActionStyle.Default, (action) => { })); Alert.AddAction(UIAlertAction.Create("Cancel", UIAlertActionStyle.Cancel, null )); UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(Alert, true, null); /* #IBAction func showChoices(_ sender: Any) { let alert = UIAlertController(title: "Car Choices", message: "\n\n\n\n\n\n", preferredStyle: .alert) alert.isModalInPopover = true let pickerFrame = UIPickerView(frame: CGRect(x: 5, y: 20, width: 250, height: 140)) alert.view.addSubview(pickerFrame) pickerFrame.dataSource = self pickerFrame.delegate = self alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (UIAlertAction) in print("You selected " + self.typeValue ) })) self.present(alert,animated: true, completion: nil ) } */ }; List<string> lstStatus = new List<string>(); lstStatus.Add(" "); lstStatus.Add("IN"); lstStatus.Add("OUT"); lstStatus.Add("N/A"); lstStatus.Add("N/O"); tvStatus.Source = new StatusSource(lstStatus); tvStatus.Layer.BorderColor = new CGColor(0, 0, 0); tvStatus.Layer.BorderWidth = 2; tvStatus.BackgroundColor = UIColor.White; tvStatus.Hidden = true; // tvStatus.Layer.ZPosition = 1; tvStatus.ResignFirstResponder(); // btnEdit.BringSubviewToFront(tvStatus); // btnEdit.Layer.ZPosition = 0; //UILabel editLabel = new UILabel(new CGRect(360, 4, 100, 33)); // Set the frame size you need //editLabel.TextColor = UIColor.Green; // Set your color //editLabel.Text = "Edit Label"; ctrls.AddSubviews(headerLabel, btnEdit, tvStatus); return ctrls; } public override nfloat GetHeightForHeader(UITableView tableView, nint section) { nfloat height = 50.0f; return height; } public override void RowSelected(UITableView tableView, NSIndexPath indexPath) { string msg = "Selected Row, ReviewInspection -- SectionIndex: " + indexPath.Section.ToString() + " RowIndex: " + indexPath.Row; tvStatus.Hidden = true; tvStatus.ResignFirstResponder(); //var Alert = UIAlertController.Create("Inspection", msg, UIAlertControllerStyle.Alert); //Alert.AddAction(UIAlertAction.Create("Ok", UIAlertActionStyle.Default, null)); //UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(Alert, true, null); } public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath) { ReportFinding currentRequirementItem = _lstCriticalObs[indexPath.Section].ReportFindings[indexPath.Row]; // _inspectionData.ReportSections[indexPath.Row]; var cell = tableView.DequeueReusableCell(_cellID) as ReviewInspectionTableCell; if (cell == null) { cell = new ReviewInspectionTableCell(_cellID); } UIView customColorView = new UIView(); customColorView.BackgroundColor = UIColor.FromRGB(39, 159, 218); UIView SelectedRowcustomColorView = new UIView(); SelectedRowcustomColorView.BackgroundColor = UIColor.FromRGB(255, 255, 255); UIColor clrChecklistButton = UIColor.White; UIColor clrFont = UIColor.White; cell.TextLabel.LineBreakMode = UILineBreakMode.WordWrap; cell.PreservesSuperviewLayoutMargins = false; cell.LayoutMargins = UIEdgeInsets.Zero; cell.BackgroundView = customColorView; cell.SelectedBackgroundView = SelectedRowcustomColorView; cell.Edit.TouchUpInside += (object sender, EventArgs e) => { string msg = "SectionIndex: " + indexPath.Section.ToString() + " RowIndex: " + indexPath.Row; var Alert = UIAlertController.Create("Inspection", msg, UIAlertControllerStyle.Alert); Alert.AddAction(UIAlertAction.Create("Ok", UIAlertActionStyle.Default, null)); UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(Alert, true, null); }; cell.Delete.TouchUpInside += (object sender, EventArgs e) => { string msg = "SectionIndex: " + indexPath.Section.ToString() + " RowIndex: " + indexPath.Row; var Alert = UIAlertController.Create("Inspection", msg, UIAlertControllerStyle.Alert); Alert.AddAction(UIAlertAction.Create("Ok", UIAlertActionStyle.Default, null)); UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(Alert, true, null); }; cell.UpdateCellControlsWithAuditData(currentRequirementItem.ViolationText, currentRequirementItem.FindingAnswer, currentRequirementItem.RepeatViolation, clrChecklistButton, clrFont); return cell; } } public class ReviewInspectionTableCell : UITableViewCell { UILabel violationText, findingAnswer, repeatViolation; UIButton btnEdit, btnDelete; public ReviewInspectionTableCell(NSString cellId) : base(UITableViewCellStyle.Default, cellId) { SelectionStyle = UITableViewCellSelectionStyle.Gray; ContentView.BackgroundColor = UIColor.FromRGB(110, 214, 254); ContentView.Layer.CornerRadius = 8; var newFont = UIFont.SystemFontOfSize(17); violationText = new UILabel(); violationText.Font = newFont; violationText.LineBreakMode = UILineBreakMode.WordWrap; violationText.TextAlignment = UITextAlignment.Left; violationText.Lines = 0; violationText.TextColor = UIColor.White; violationText.HighlightedTextColor = UIColor.Green; violationText.BackgroundColor = UIColor.Gray; findingAnswer = new UILabel(); findingAnswer.Font = newFont; findingAnswer.BackgroundColor = UIColor.Orange; findingAnswer.LineBreakMode = UILineBreakMode.WordWrap; findingAnswer.TextAlignment = UITextAlignment.Left; findingAnswer.Lines = 0; findingAnswer.TextColor = UIColor.White; repeatViolation = new UILabel(); repeatViolation.Font = newFont; repeatViolation.BackgroundColor = UIColor.Purple; btnEdit = new UIButton(); btnEdit.Font = newFont; btnEdit.SetTitle("Edit", UIControlState.Normal); btnEdit.SetTitleColor(UIColor.Black, UIControlState.Selected); btnDelete = new UIButton(); btnDelete.Font = newFont; btnDelete.SetTitle("Delete", UIControlState.Normal); btnDelete.SetTitleColor(UIColor.Black, UIControlState.Selected); ContentView.AddSubviews(new UIView[] { violationText, findingAnswer, repeatViolation, btnEdit, btnDelete }); } public UIButton Delete { get { return btnDelete; } } public UIButton Edit { get { return btnEdit; } } public void UpdateCellControlsWithAuditData(string _violationText, string _findingAnswer, Boolean _repeatVilation, UIColor btnColor, UIColor clrFont) { string repeat = string.Empty; if (_repeatVilation) { repeat = "Repeat Violation."; } violationText.Text = _violationText; findingAnswer.Text = _findingAnswer; repeatViolation.Text = repeat; } public override void LayoutSubviews() { base.LayoutSubviews(); //x, y, width, height violationText.Frame = new CGRect(50, 0, 700, 80); findingAnswer.Frame = new CGRect(50, 81, 700, 33); repeatViolation.Frame = new CGRect(50, 110, 350, 33); btnEdit.Frame = new CGRect(5, 0, 50, 33); btnEdit.Layer.CornerRadius = 8; btnEdit.BackgroundColor = UIColor.FromRGB(19, 120, 170); btnDelete.Frame = new CGRect(5, 34, 50, 33); btnDelete.Layer.CornerRadius = 8; btnDelete.BackgroundColor = UIColor.FromRGB(19, 120, 170); } } public class ObsStatusModel : UIPickerViewModel { static string[] names = new string[] { "Monitor", "Comprehensive", "OutBreak Investigation", "Complaint", "FollowUp", "PlanReview", "Other" }; public ObsStatusModel() { } public override nint GetComponentCount(UIPickerView v) { return 1; } public override nint GetRowsInComponent(UIPickerView pickerView, nint component) { return names.Length; } public override string GetTitle(UIPickerView picker, nint row, nint component) { return names[row]; } } class StatusSource : UITableViewSource { NSString _cellID = new NSString("TableCell"); private List<string> _lstStatus; public StatusSource(List<string> lstStatus) { _lstStatus = lstStatus; } public override nint RowsInSection(UITableView tableview, nint section) { return _lstStatus.Count; } public override void RowSelected(UITableView tableView, NSIndexPath indexPath) { var Alert = UIAlertController.Create("Inspection", string.Empty, UIAlertControllerStyle.Alert); Alert.AddAction(UIAlertAction.Create("Ok", UIAlertActionStyle.Default, null)); UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(Alert, true, null); } public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath) { string currentStatusItem = _lstStatus[indexPath.Row]; var cell = tableView.DequeueReusableCell(_cellID) as StatusTableCell; if (cell == null) { cell = new StatusTableCell(_cellID); } cell.TextLabel.LineBreakMode = UILineBreakMode.WordWrap; cell.PreservesSuperviewLayoutMargins = false; cell.LayoutMargins = UIEdgeInsets.Zero; cell.UpdateCellControlsWithAuditData(currentStatusItem); return cell; } } public class StatusTableCell : UITableViewCell { UILabel statusText; public StatusTableCell(NSString cellId) : base(UITableViewCellStyle.Default, cellId) { SelectionStyle = UITableViewCellSelectionStyle.Gray; //ContentView.BackgroundColor = UIColor.FromRGB(110, 214, 254); //ContentView.Layer.CornerRadius = 8; var newFont = UIFont.SystemFontOfSize(17); statusText = new UILabel(); statusText.Font = newFont; statusText.LineBreakMode = UILineBreakMode.WordWrap; statusText.TextAlignment = UITextAlignment.Center; statusText.Lines = 0; statusText.TextColor = UIColor.Black; statusText.HighlightedTextColor = UIColor.Green; statusText.BackgroundColor = UIColor.White; ContentView.AddSubviews(new UIView[] { statusText }); } public void UpdateCellControlsWithAuditData(string status) { statusText.Text = status; } public override void LayoutSubviews() { base.LayoutSubviews(); //x, y, width, height statusText.Frame = new CGRect(5, 5, 100, 25); } } } See attached image.
You can add a UIPickerView as subview on the view of UIAlertViewController. Refer to the following code: public void CreatAlertView() { UIAlertController alert = UIAlertController.Create("Inspection\n\n\n\n\n\n\n", "", UIAlertControllerStyle.Alert); alert.AddTextField((UITextField obj) => { obj.Placeholder = "email address"; }); UIAlertAction OKAction = UIAlertAction.Create("OK", UIAlertActionStyle.Default, (obj) => { if(alert.TextFields[0].Text.Length!=0) { //do some thing } }); UIAlertAction CancelAction = UIAlertAction.Create("Cancel", UIAlertActionStyle.Cancel, null); alert.AddAction(OKAction); alert.AddAction(CancelAction); var picker = new UIPickerView(new CGRect(0, 0,270,220)); picker.WeakDelegate = this; picker.DataSource = this; this.pickerView = picker; alert.View.AddSubview(pickerView); alertController = alert; this.PresentViewController(alertController, false, null); } And don't forget to implement the methods in the delegate: public nint GetComponentCount(UIPickerView pickerView) { return 1; } public nint GetRowsInComponent(UIPickerView pickerView, nint component) { if (component==0) { return (System.nint)itemsArray.Length; } else { return 0; } } [Export("pickerView:titleForRow:forComponent:")] public string GetTitle(UIPickerView pickerView, nint row, nint component) { string content = itemsArray[(nuint)row].ToString(); return content; } [Export("pickerView:didSelectRow:inComponent:")] public void Selected(UIPickerView pickerView, nint row, nint component) { string content= itemsArray[(nuint)row].ToString(); //do some thing } Then you can call the method this.itemsArray= new string[] {"apple","google","apple","google","apple","google"} ;//init the source array CreatAlertView(); I have upload the demo to my git. you can download it for more details.demo
Animate loading spinner arc Xamarin.iOS
In my Xamarin.iOS application, I am trying to animate a loading spinner. I can draw the circle and the arc, but I do not know how to animate it. This is my class for the loading spinner: public class LoadingSpinnerView : UIView { private CAShapeLayer _thinCirlce; private CAShapeLayer _arc; public LoadingSpinnerView() { _arc = new CAShapeLayer(); _arc.LineWidth = 3; _arc.StrokeColor = UIColor.Blue.CGColor; _arc.FillColor = UIColor.Clear.CGColor; _thinCirlce = new CAShapeLayer(); _thinCirlce.LineWidth = 1; _thinCirlce.StrokeColor = UIColor.Red.CGColor; _thinCirlce.FillColor = UIColor.Clear.CGColor; Layer.AddSublayer(_thinCirlce); Layer.AddSublayer(_arc); } private nfloat _angle; public nfloat Angle { get { return _angle; } set { _angle = value; } } public override void LayoutSubviews() { base.LayoutSubviews(); _thinCirlce.Path = UIBezierPath.FromOval(new CoreGraphics.CGRect(0, 0, Frame.Width, Frame.Height)).CGPath; nfloat radius = Frame.Width / 2; _arc.Path = UIBezierPath.FromArc(new CoreGraphics.CGPoint(radius, radius), radius, 0, Angle, true).CGPath; } } I want to be able to animate it, something like this: UIView.Animate(5, () => { _loadingSpinnerView.Angle = 3.14f; }); // This doesn't actually work...
Ready to use control. Just set control to UIView in your Storyboard. [Register(nameof(CircleLoadingView))] public class CircleLoadingView : UIView { public CircleLoadingView(IntPtr handle) : base(handle) { } public override void LayoutSubviews() { base.LayoutSubviews(); SetUpAnimation(this.Layer, new CGSize(Frame.Width, Frame.Height), UIColor.Red); } public void SetUpAnimation(CALayer layer, CGSize size, UIColor color) { var beginTime = 0.5; var strokeStartDuration = 1.2; var strokeEndDuration = 0.7; var strokeEndAnimation = CABasicAnimation.FromKeyPath("strokeEnd"); strokeEndAnimation.Duration = strokeEndDuration; strokeEndAnimation.TimingFunction = CAMediaTimingFunction.FromControlPoints(0.4f, 0.0f, 0.2f, 1.0f); strokeEndAnimation.From = NSNumber.FromFloat(0); strokeEndAnimation.To = NSNumber.FromFloat(1); var strokeStartAnimation = CABasicAnimation.FromKeyPath("strokeStart"); strokeStartAnimation.Duration = strokeStartDuration; strokeStartAnimation.TimingFunction = CAMediaTimingFunction.FromControlPoints(0.4f, 0.0f, 0.2f, 1.0f); strokeStartAnimation.From = NSNumber.FromFloat(0); strokeStartAnimation.To = NSNumber.FromFloat(1); strokeStartAnimation.BeginTime = beginTime; var groupAnimation = new CAAnimationGroup { Animations = new CAAnimation[] {/*rotationAnimation,*/ strokeEndAnimation, strokeStartAnimation }, Duration = strokeStartDuration + beginTime, RepeatCount = float.PositiveInfinity, RemovedOnCompletion = false, FillMode = CAFillMode.Forwards }; var circle = CreateCircle(size, color); var frame = CGRect.FromLTRB( (layer.Bounds.Width - size.Width) / 2, (layer.Bounds.Height - size.Height) / 2, size.Width, size.Height ); circle.Frame = frame; circle.AddAnimation(groupAnimation, "animation"); layer.AddSublayer(circle); } private CAShapeLayer CreateCircle(CGSize size, UIColor color) { var layer = new CAShapeLayer(); var path = new UIBezierPath(); path.AddArc(new CGPoint(size.Width / 2, size.Height / 2), size.Width / 2, -(float)(Math.PI / 2), (float)(Math.PI + Math.PI / 2), true); layer.FillColor = null; layer.StrokeColor = color.CGColor; layer.LineWidth = 2; layer.BackgroundColor = null; layer.Path = path.CGPath; layer.Frame = CGRect.FromLTRB(0, 0, size.Width, size.Height); return layer; } }
I figured it out. Here is my loading spinner class for other Xamarin developers to use. You can adjust it to your own requirements. public class LoadingSpinnerView : UIView { private CAShapeLayer _thinCirlce; private CAShapeLayer _arcLayer; public LoadingSpinnerView(nfloat radius) { UIColor.Red.SetColor(); _thinCirlce = new CAShapeLayer(); _thinCirlce.LineWidth = 1; _thinCirlce.Path = UIBezierPath.FromOval(new CoreGraphics.CGRect(0, 0, radius * 2, radius * 2)).CGPath; _thinCirlce.StrokeColor = UIColor.Red.CGColor; _thinCirlce.FillColor = UIColor.Clear.CGColor; Layer.AddSublayer(_thinCirlce); UIColor.Blue.SetColor(); UIBezierPath arcPath = new UIBezierPath(); arcPath.LineWidth = 4; arcPath.LineCapStyle = CGLineCap.Round; arcPath.LineJoinStyle = CGLineJoin.Round; arcPath.AddArc(new CoreGraphics.CGPoint(radius, radius), radius, 0, 2 * 3.14f, true); _arcLayer = new CAShapeLayer(); _arcLayer.Path = arcPath.CGPath; _arcLayer.StrokeColor = UIColor.Blue.CGColor; _arcLayer.FillColor = UIColor.Clear.CGColor; _arcLayer.LineWidth = 4; _arcLayer.StrokeStart = 0; _arcLayer.StrokeEnd = 1; if (_arcLayer.SuperLayer != null) { _arcLayer.RemoveAllAnimations(); _arcLayer.RemoveFromSuperLayer(); } } public void StartAnimation() { Layer.AddSublayer(_arcLayer); CABasicAnimation animation = new CABasicAnimation(); animation.KeyPath = "strokeEnd"; animation.Duration = 3; animation.From = NSNumber.FromFloat(0); animation.To = NSNumber.FromFloat(1); _arcLayer.AddAnimation(animation, null); } public void StopAnimation() { _arcLayer.RemoveAllAnimations(); _arcLayer.RemoveFromSuperLayer(); } }
Which options do I have for longer option strings?
As we can see on the bottom of the screen, there are options to be chosen. Which other options are there for this menu such that I can display longer strings fully?
For your requirement you have to make a customize one to do it: Try to use this code: using System; using System.Collections.Generic; using CoreGraphics; using UIKit; namespace CusActionSheet { public delegate void OneParameterEventHandler<T>(T data); public class ALActionSheet : UIView { private nfloat maximumProportion = 0.8f; public event OneParameterEventHandler<string> RowClicked; //You also can use this if you don't know the generic(but remeber also change it for source): //public delegate void OneParameterEventHandler(string data); //public event OneParameterEventHandler SelectedPhoneNumber; private UIView backgroundContainer; private UIView topContainer; private UILabel topContainerTitleView; private UITableView topContainerTable; private ALActionSheetSource topContainerTableSource; private UIButton btnCancel; private nfloat itemPadding = 10; private nfloat itemHeight = 60; private nfloat cornerRadius = 10; private List<string> dataList; private CGRect backContainerShownFrame; private CGRect backContainerHiddenFrame; private string selectedNumber; public CGRect BackContainerShownFrame { get { return backContainerShownFrame; } set { backContainerShownFrame = value; } } public ALActionSheet(List<string> _dataList) { this.BackgroundColor = UIColor.Clear; this.dataList = _dataList; backgroundContainer = new UIView(); backgroundContainer.Layer.CornerRadius = cornerRadius; backgroundContainer.Layer.MasksToBounds = true; topContainer = new UIView(); topContainer.BackgroundColor = UIColor.White; topContainer.Layer.CornerRadius = cornerRadius; topContainer.Layer.MasksToBounds = true; topContainerTitleView = new UILabel(); topContainerTitleView.Text = "Select a number"; topContainerTitleView.TextAlignment = UITextAlignment.Center; topContainerTitleView.BackgroundColor = UIColor.FromRGB(230, 230, 230); topContainer.AddSubview(topContainerTitleView); topContainerTableSource = new ALActionSheetSource(dataList); topContainerTableSource.RowClicked += (data) => { selectedNumber = data; Close(); }; topContainerTable = new UITableView(); topContainerTable.RowHeight = itemHeight; topContainerTable.Layer.CornerRadius = cornerRadius; topContainerTable.Layer.MasksToBounds = true; topContainerTable.BackgroundColor = UIColor.White; topContainerTable.Source = topContainerTableSource; topContainer.AddSubview(topContainerTable); btnCancel = new UIButton(UIButtonType.System); btnCancel.SetTitle("Cancel", UIControlState.Normal); btnCancel.TitleLabel.Font = UIFont.SystemFontOfSize(18); btnCancel.BackgroundColor = UIColor.White; btnCancel.Layer.CornerRadius = cornerRadius; btnCancel.Layer.MasksToBounds = true; btnCancel.TouchUpInside += delegate { Close(); }; backgroundContainer.AddSubview(topContainer); backgroundContainer.AddSubview(btnCancel); Layout(); } public void Layout() { this.Frame = UIScreen.MainScreen.Bounds; nfloat tableMaxHeight = UIScreen.MainScreen.Bounds.Height * maximumProportion - 2 * itemPadding - 2 * itemHeight; nfloat tableHeight = dataList.Count * itemHeight < tableMaxHeight ? dataList.Count * itemHeight : tableMaxHeight; nfloat itemWidth = UIScreen.MainScreen.Bounds.Width - 2 * itemPadding; nfloat backgroundContainerHeight = tableHeight + 2 * itemHeight + itemPadding; backgroundContainer.Frame = new CGRect(0, 0, itemWidth, backgroundContainerHeight); nfloat topContainerHeight = itemHeight + tableHeight; topContainer.Frame = new CGRect(0, 0, itemWidth, topContainerHeight); topContainerTitleView.Frame = new CGRect(0, 0, itemWidth, itemHeight); topContainerTable.Frame = new CGRect(0, itemHeight, itemWidth, tableHeight); btnCancel.Frame = new CGRect(0, topContainerHeight + itemPadding, itemWidth, itemHeight); BackContainerShownFrame = new CGRect(new CGPoint(itemPadding, UIScreen.MainScreen.Bounds.Height - backgroundContainerHeight - itemPadding), backgroundContainer.Bounds.Size); backContainerHiddenFrame = new CGRect(new CGPoint(itemPadding, UIScreen.MainScreen.Bounds.Height + itemPadding), backgroundContainer.Bounds.Size); } public void Show() { UIApplication.SharedApplication.KeyWindow.AddSubview(this); backgroundContainer.Frame = backContainerHiddenFrame; this.AddSubview(backgroundContainer); UIView.Animate(0.3, delegate { this.BackgroundColor = UIColor.FromRGBA(0, 0, 0, 0.5f); backgroundContainer.Frame = BackContainerShownFrame; }); } public void Close() { UIView.Animate(0.3, delegate { backgroundContainer.Frame = backContainerHiddenFrame; this.BackgroundColor = UIColor.Clear; }, delegate { this.RemoveFromSuperview(); if (null != RowClicked && null != selectedNumber) RowClicked(selectedNumber); }); } } class ALActionSheetSource : UITableViewSource { public event OneParameterEventHandler<string> RowClicked; private string cellID = "ALActionSheetCell"; private List<string> dataList; public ALActionSheetSource(List<string> _dataList) { dataList = _dataList; } public override nint RowsInSection(UITableView tableview, nint section) { return dataList.Count; } public override UITableViewCell GetCell(UITableView tableView, Foundation.NSIndexPath indexPath) { UITableViewCell cell = tableView.DequeueReusableCell(cellID); if (null == cell) { cell = new UITableViewCell(UITableViewCellStyle.Default, cellID); cell.TextLabel.TextAlignment = UITextAlignment.Center; //cell.TextLabel.AdjustsFontSizeToFitWidth = true;//Auto resize by content cell.TextLabel.Lines = int.MaxValue;//Multiple lines } cell.TextLabel.Text = dataList[indexPath.Row]; return cell; } public override void RowSelected(UITableView tableView, Foundation.NSIndexPath indexPath) { if (null != RowClicked) RowClicked(dataList[indexPath.Row]); } } } And invoke it like this: public override void ViewDidLoad() { base.ViewDidLoad(); // Perform any additional setup after loading the view, typically from a nib. List<string> testList = new List<string>(); for (int i = 0; i < 10; i++) { testList.Add("Item " + i.ToString()); } testList.Add("I'm a very very very very very very very very long text."); ALActionSheet actionSheet = new ALActionSheet(testList); actionSheet.RowClicked += (data) => { Console.WriteLine("data = "+data); }; UIButton btnTest = new UIButton(UIButtonType.System); btnTest.SetTitle("Test", UIControlState.Normal); btnTest.Frame = new CoreGraphics.CGRect(50, 50, 80, 30); btnTest.TouchUpInside += delegate { actionSheet.Show(); }; this.Add(btnTest); } Hope it can help you.
Custom Rendering on IOS a CardView from Frame
I am trying to make a CardView on IOS Xamarin Forms by subclassing the Frame and making a custom renderer. I want to achieve something like this: Upon looking into the API on setting a shadow, I've done this: [assembly: ExportRenderer(typeof(CardView), typeof(CardViewRenderer))] namespace TrabbleMobile.iOS.CustomRenderers { public class CardViewRenderer : FrameRenderer { public override void Draw(CGRect rect) { var cardView = (CardView)this.Element; using (var context = UIGraphics.GetCurrentContext()) { //nfloat cornerRadius = 2; float shadowOffsetWidth = 2; float shadowOffsetHeight = 4; var shadowColor = new CGColor(0, 0, 0, (nfloat)0.5); var boxColor = new CGColor(255, 255, 255); var shadowBlur = (float)0.5; var offset = new CGSize(shadowOffsetWidth, shadowOffsetHeight); context.SetShadow(offset, shadowBlur, shadowColor); However, it does not render as it should and no shadow at all.
I've done this, and the way that I did this is thru custom rendering on IOS and here is the custom renderer code: public class CardViewRenderer : FrameRenderer { public override void Draw(CGRect rect) { SetupShadowLayer(); base.Draw(rect); } void SetupShadowLayer() { Layer.CornerRadius = 2; // 5 Default if (Element.BackgroundColor == Xamarin.Forms.Color.Default) { Layer.BackgroundColor = UIColor.White.CGColor; } else { Layer.BackgroundColor = Element.BackgroundColor.ToCGColor(); } Layer.ShadowRadius = 2; // 5 Default Layer.ShadowColor = UIColor.Black.CGColor; Layer.ShadowOpacity = 0.4f; // 0.8f Default Layer.ShadowOffset = new CGSize(0f, 2.5f); if (Element.OutlineColor == Xamarin.Forms.Color.Default) { Layer.BorderColor = UIColor.Clear.CGColor; } else { Layer.BorderColor = Element.OutlineColor.ToCGColor(); Layer.BorderWidth = 1; } Layer.RasterizationScale = UIScreen.MainScreen.Scale; Layer.ShouldRasterize = true; } }
UISnapBehavior strange jiggle - swift
I am trying to use UISnapBehavior to create a tweening back animation. I am trying to make a square draggable, then when the square is released, it tweens back to its original position. Each time it tweens back, it seems to have a strange trajectory and rather than easing back at the correct angleit seems to ease back in a sort of strange semi circle before reaching its destination. Thank you in advance. my code is as follows: var animator:UIDynamicAnimator? = nil; var ball : Ball!; var ball2 : Ball!; var snap: UISnapBehavior! var gravity : UIGravityBehavior!; var speed : Double! = 0; var lastTouchPoint : CGPoint!; var attach : UIAttachmentBehavior! = nil; var itemBehavior : UIDynamicItemBehavior!; var snapping : UISnapBehavior! = nil; var currentBall : Ball! = nil; override init(frame: CGRect) { super.init(frame: frame); println( "-- initiated view --" ); self.backgroundColor = UIColor.orangeColor(); ball = Ball( frame: CGRect(x: 200, y: 200, width: 50, height: 50 ) ); self.addSubview(ball); ball2 = Ball( frame: CGRect(x: 100, y: 100, width: 50, height: 50 ) ); self.addSubview(ball2); setup() self.addGestureRecognizer( UIPanGestureRecognizer(target: self, action: "panHandler:") ); self.attach = UIAttachmentBehavior(item: self, attachedToAnchor: CGPoint(x: 0, y: 0) ); } func setup() { animator = UIDynamicAnimator(referenceView:self); var collision: UICollisionBehavior! collision = UICollisionBehavior(items: [ball, ball2]) collision.translatesReferenceBoundsIntoBoundary = false; collision.collisionDelegate = self; animator?.addBehavior(collision) var dynamicItem : UIDynamicItemBehavior = UIDynamicItemBehavior(items: [ball, ball2] ); dynamicItem.allowsRotation = false; self.animator!.addBehavior(dynamicItem); } func panHandler( recognizr : UIPanGestureRecognizer ) { switch( recognizr.state ) { case UIGestureRecognizerState.Began: let location = recognizr.locationInView(self); let view : AnyObject? = self.hitTest(location, withEvent: nil); if( view is Ball ) { currentBall = view as! Ball; if( self.attach != nil ){ self.animator!.removeBehavior(attach); self.attach = nil; } self.attach = UIAttachmentBehavior(item: currentBall, attachedToAnchor: location ); self.animator!.addBehavior(self.attach); self.attach.damping = 1; self.attach.length = 1; let dynamicItem : UIDynamicItemBehavior = UIDynamicItemBehavior(items: [currentBall] ); dynamicItem.allowsRotation = false; self.animator!.addBehavior(dynamicItem); } break; case UIGestureRecognizerState.Changed : if( currentBall != nil ) { let location = recognizr.locationInView(self); self.attach.anchorPoint = location; } break; default : println("--- snap ---"); if( self.currentBall == nil ) {return;} self.animator!.removeBehavior(attach); self.attach = nil; var snap : UISnapBehavior = UISnapBehavior(item: self.currentBall, snapToPoint: self.currentBall.startingPoint ); snap.damping = 1; self.animator!.addBehavior(snap); break; } }
It's 2018 now and I'm seeing pretty much the same movement. I think it's a bug in Apple's implementation and you and I both should file a bug report since it's the only way this will get fixed. In my experience, this jiggle in a semi-circle movement toward the end appears more pronounced when damping is higher than 1.0. In my example below, my gesture recognizer strictly fixes the x-axis value, so theoretically there should not be any horizontal movement. allowsRotation is set to false.