├── README.md └── iOS ├── ELCImagePicker ├── ELCImagePickerViewController.cs └── Resources │ ├── Overlay.png │ └── Overlay@2x.png └── Toast └── Toast.cs /README.md: -------------------------------------------------------------------------------- 1 | XamarinSharpPlus 2 | =============== 3 | 4 | A collection of libraries for use with Xamarin. These projects are designed to be easy to drop in to your Xamarin project. 5 | For example, where possible we port to C# rather than creating a binding and avoid extra dependencies when possible. 6 | 7 | iOS Projects 8 | ----------- 9 | * Toast: An Android-like 'toasting' library ported to C#. 10 | 11 | * ELCImagePicker: A popular multi-photo picker ported to C#. 12 | 13 | https://github.com/B-Sides/ELCImagePickerController -------------------------------------------------------------------------------- /iOS/ELCImagePicker/ELCImagePickerViewController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MonoTouch.UIKit; 3 | using MonoTouch.AssetsLibrary; 4 | using Vernacular; 5 | using System.Collections.Generic; 6 | using System.Drawing; 7 | using MonoTouch.Foundation; 8 | using System.Threading.Tasks; 9 | 10 | namespace ELCPicker 11 | { 12 | public class AssetResult 13 | { 14 | public UIImage Image { get; set; } 15 | } 16 | 17 | 18 | /** 19 | * Presents a photo picker dialog capable of selecting multiple images at once. 20 | * Usage: 21 | * 22 | * var picker = ELCImagePickerViewController.Instance; 23 | * picker.MaximumImagesCount = 15; 24 | * picker.Completion.ContinueWith (t => { 25 | * if (t.IsCancelled || t.Exception != null) { 26 | * // no pictures for you! 27 | * } else { 28 | * // t.Result is a List 29 | * } 30 | * }); 31 | * 32 | * PresentViewController (picker, true, null); 33 | */ 34 | public class ELCImagePickerViewController : UINavigationController 35 | { 36 | public int MaximumImagesCount { get; set; } 37 | 38 | readonly TaskCompletionSource> _TaskCompletionSource = new TaskCompletionSource> (); 39 | public Task> Completion { 40 | get { 41 | return _TaskCompletionSource.Task; 42 | } 43 | } 44 | 45 | public static ELCImagePickerViewController Instance { 46 | get { 47 | var albumPicker = new ELCAlbumPickerController (); 48 | var picker = new ELCImagePickerViewController (albumPicker); 49 | albumPicker.Parent = picker; 50 | picker.MaximumImagesCount = 4; 51 | picker.NavigationBar.BarStyle = UIBarStyle.Black; 52 | return picker; 53 | } 54 | } 55 | 56 | ELCImagePickerViewController (UIViewController rootController) : base (rootController) 57 | { 58 | 59 | } 60 | 61 | void SelectedAssets (List assets) 62 | { 63 | var results = new List (assets.Count); 64 | foreach (var asset in assets) { 65 | var obj = asset.AssetType; 66 | if (obj == default (ALAssetType)) 67 | continue; 68 | 69 | var rep = asset.DefaultRepresentation; 70 | if (rep != null) { 71 | var result = new AssetResult (); 72 | UIImageOrientation orientation = UIImageOrientation.Up; 73 | var cgImage = rep.GetFullScreenImage (); 74 | result.Image = new UIImage (cgImage, 1.0f, orientation); 75 | results.Add (result); 76 | } 77 | } 78 | 79 | _TaskCompletionSource.TrySetResult (results); 80 | } 81 | 82 | void CancelledPicker () 83 | { 84 | _TaskCompletionSource.TrySetCanceled (); 85 | } 86 | 87 | bool ShouldSelectAsset (ALAsset asset, int previousCount) 88 | { 89 | var shouldSelect = MaximumImagesCount <= 0 || previousCount < MaximumImagesCount; 90 | if (!shouldSelect) { 91 | string title = Catalog.Format (Catalog.GetString ("Only {0} photos please!"), MaximumImagesCount); 92 | string message = Catalog.Format (Catalog.GetString ("You can only send {0} photos at a time."), MaximumImagesCount); 93 | var alert = new UIAlertView (title, message, null, null, Catalog.GetString ("Okay")); 94 | alert.Show (); 95 | } 96 | return shouldSelect; 97 | } 98 | 99 | public class ELCAlbumPickerController : UITableViewController 100 | { 101 | static readonly NSObject _Dispatcher = new NSObject(); 102 | readonly List AssetGroups = new List (); 103 | 104 | ALAssetsLibrary Library; 105 | 106 | WeakReference _Parent; 107 | public ELCImagePickerViewController Parent { 108 | get { 109 | return _Parent == null ? null : _Parent.Target as ELCImagePickerViewController; 110 | } 111 | set { 112 | _Parent = new WeakReference (value); 113 | } 114 | } 115 | 116 | public ELCAlbumPickerController () 117 | { 118 | } 119 | 120 | public override void ViewDidLoad () 121 | { 122 | base.ViewDidLoad (); 123 | 124 | NavigationItem.Title = Catalog.GetString ("Loading..."); 125 | var cancelButton = new UIBarButtonItem (UIBarButtonSystemItem.Cancel/*, cancelImagePicker*/); 126 | cancelButton.Clicked += CancelClicked; 127 | NavigationItem.RightBarButtonItem = cancelButton; 128 | 129 | AssetGroups.Clear (); 130 | 131 | Library = new ALAssetsLibrary (); 132 | Library.Enumerate (ALAssetsGroupType.All, GroupsEnumerator, GroupsEnumeratorFailed); 133 | } 134 | 135 | public override void ViewDidDisappear (bool animated) 136 | { 137 | base.ViewDidDisappear (animated); 138 | if (IsMovingFromParentViewController || IsBeingDismissed) { 139 | NavigationItem.RightBarButtonItem.Clicked -= CancelClicked; 140 | } 141 | } 142 | 143 | void CancelClicked (object sender = null, EventArgs e = null) 144 | { 145 | var parent = Parent; 146 | if (parent != null) { 147 | parent.CancelledPicker (); 148 | } 149 | } 150 | 151 | void GroupsEnumeratorFailed (MonoTouch.Foundation.NSError error) 152 | { 153 | Console.WriteLine ("Enumerator failed!"); 154 | } 155 | 156 | void GroupsEnumerator (ALAssetsGroup agroup, ref bool stop) 157 | { 158 | if (agroup == null) { 159 | return; 160 | } 161 | 162 | // added fix for camera albums order 163 | if (agroup.Name.ToString ().ToLower () == "camera roll" && agroup.Type == ALAssetsGroupType.SavedPhotos) { 164 | AssetGroups.Insert (0, agroup); 165 | } else { 166 | AssetGroups.Add (agroup); 167 | } 168 | 169 | _Dispatcher.BeginInvokeOnMainThread (ReloadTableView); 170 | } 171 | 172 | void ReloadTableView () 173 | { 174 | TableView.ReloadData (); 175 | NavigationItem.Title = Catalog.GetString ("Select an Album"); 176 | } 177 | 178 | public override int NumberOfSections (UITableView tableView) 179 | { 180 | return 1; 181 | } 182 | 183 | public override int RowsInSection (UITableView tableview, int section) 184 | { 185 | return AssetGroups.Count; 186 | } 187 | 188 | public override UITableViewCell GetCell (UITableView tableView, MonoTouch.Foundation.NSIndexPath indexPath) 189 | { 190 | const string cellIdentifier = "Cell"; 191 | 192 | var cell = tableView.DequeueReusableCell (cellIdentifier); 193 | if (cell == null) { 194 | cell = new UITableViewCell (UITableViewCellStyle.Default, cellIdentifier); 195 | } 196 | 197 | // Get count 198 | var g = AssetGroups [indexPath.Row]; 199 | g.SetAssetsFilter (ALAssetsFilter.AllPhotos); 200 | var gCount = g.Count; 201 | cell.TextLabel.Text = string.Format ("{0} ({1})", g.Name, gCount); 202 | try { 203 | cell.ImageView.Image = new UIImage (g.PosterImage); 204 | } catch (Exception e) { 205 | Console.WriteLine ("Failed to set thumbnail {0}", e); 206 | } 207 | cell.Accessory = UITableViewCellAccessory.DisclosureIndicator; 208 | 209 | return cell; 210 | } 211 | 212 | public override void RowSelected (UITableView tableView, NSIndexPath indexPath) 213 | { 214 | var assetGroup = AssetGroups [indexPath.Row]; 215 | assetGroup.SetAssetsFilter (ALAssetsFilter.AllPhotos); 216 | var picker = new ELCAssetTablePicker (assetGroup); 217 | picker.Parent = Parent; 218 | NavigationController.PushViewController (picker, true); 219 | } 220 | 221 | public override float GetHeightForRow (UITableView tableView, NSIndexPath indexPath) 222 | { 223 | return 57; 224 | } 225 | } 226 | 227 | 228 | class ELCAssetTablePicker : UITableViewController 229 | { 230 | static readonly NSObject _Dispatcher = new NSObject(); 231 | 232 | int Columns = 4; 233 | public bool SingleSelection { get; set; } 234 | public bool ImmediateReturn { get; set; } 235 | readonly ALAssetsGroup AssetGroup; 236 | 237 | readonly List ElcAssets = new List (); 238 | 239 | WeakReference _Parent; 240 | public ELCImagePickerViewController Parent { 241 | get { 242 | return _Parent == null ? null : _Parent.Target as ELCImagePickerViewController; 243 | } 244 | set { 245 | _Parent = new WeakReference (value); 246 | } 247 | } 248 | 249 | public ELCAssetTablePicker (ALAssetsGroup assetGroup) 250 | { 251 | AssetGroup = assetGroup; 252 | } 253 | 254 | public override void ViewDidLoad () 255 | { 256 | TableView.SeparatorStyle = UITableViewCellSeparatorStyle.None; 257 | TableView.AllowsSelection = false; 258 | 259 | if (ImmediateReturn) { 260 | 261 | } else { 262 | var doneButtonItem = new UIBarButtonItem (UIBarButtonSystemItem.Done); 263 | doneButtonItem.Clicked += DoneClicked; 264 | NavigationItem.RightBarButtonItem = doneButtonItem; 265 | NavigationItem.Title = Catalog.GetString ("Loading..."); 266 | } 267 | 268 | Task.Run ((Action)PreparePhotos); 269 | } 270 | 271 | public override void ViewWillAppear (bool animated) 272 | { 273 | base.ViewWillAppear (animated); 274 | Columns = (int)(View.Bounds.Size.Width / 80f); 275 | } 276 | 277 | public override void ViewDidDisappear (bool animated) 278 | { 279 | base.ViewDidDisappear (animated); 280 | if (IsMovingFromParentViewController || IsBeingDismissed) { 281 | NavigationItem.RightBarButtonItem.Clicked -= DoneClicked; 282 | } 283 | } 284 | 285 | public override void DidRotate (UIInterfaceOrientation fromInterfaceOrientation) 286 | { 287 | base.DidRotate (fromInterfaceOrientation); 288 | Columns = (int)(View.Bounds.Size.Width / 80f); 289 | TableView.ReloadData (); 290 | } 291 | 292 | void PreparePhotos () 293 | { 294 | AssetGroup.Enumerate (PhotoEnumerator); 295 | 296 | _Dispatcher.BeginInvokeOnMainThread (() => { 297 | TableView.ReloadData (); 298 | // scroll to bottom 299 | int section = NumberOfSections (TableView) - 1; 300 | int row = TableView.NumberOfRowsInSection (section) - 1; 301 | if (section >= 0 && row >= 0) { 302 | var ip = NSIndexPath.FromRowSection (row, section); 303 | TableView.ScrollToRow (ip, UITableViewScrollPosition.Bottom, false); 304 | } 305 | NavigationItem.Title = SingleSelection ? Catalog.GetString ("Pick Photo") : Catalog.GetString ("Pick Photos"); 306 | }); 307 | } 308 | 309 | void PhotoEnumerator (ALAsset result, int index, ref bool stop) 310 | { 311 | if (result == null) { 312 | return; 313 | } 314 | 315 | ELCAsset elcAsset = new ELCAsset (this, result); 316 | 317 | bool isAssetFiltered = false; 318 | /*if (self.assetPickerFilterDelegate && 319 | [self.assetPickerFilterDelegate respondsToSelector:@selector(assetTablePicker:isAssetFilteredOut:)]) 320 | { 321 | isAssetFiltered = [self.assetPickerFilterDelegate assetTablePicker:self isAssetFilteredOut:(ELCAsset*)elcAsset]; 322 | }*/ 323 | 324 | if (result.DefaultRepresentation == null) 325 | isAssetFiltered = true; 326 | 327 | if (!isAssetFiltered) { 328 | ElcAssets.Add (elcAsset); 329 | } 330 | } 331 | 332 | void DoneClicked (object sender = null, EventArgs e = null) 333 | { 334 | var selected = new List (); 335 | 336 | foreach (var asset in ElcAssets) { 337 | if (asset.Selected) { 338 | selected.Add (asset.Asset); 339 | } 340 | } 341 | 342 | var parent = Parent; 343 | if (parent != null) { 344 | parent.SelectedAssets (selected); 345 | } 346 | } 347 | 348 | bool ShouldSelectAsset (ELCAsset asset) 349 | { 350 | int selectionCount = TotalSelectedAssets; 351 | bool shouldSelect = true; 352 | 353 | var parent = Parent; 354 | if (parent != null) { 355 | shouldSelect = parent.ShouldSelectAsset (asset.Asset, selectionCount); 356 | } 357 | 358 | return shouldSelect; 359 | } 360 | 361 | void AssetSelected (ELCAsset asset, bool selected) 362 | { 363 | TotalSelectedAssets += (selected) ? 1 : -1; 364 | 365 | if (SingleSelection) { 366 | foreach (var elcAsset in ElcAssets) { 367 | if (asset != elcAsset) { 368 | elcAsset.Selected = false; 369 | } 370 | } 371 | } 372 | if (ImmediateReturn) { 373 | var parent = Parent; 374 | var obj = new List (1); 375 | obj.Add (asset.Asset); 376 | parent.SelectedAssets (obj); 377 | } 378 | } 379 | 380 | public override int NumberOfSections (UITableView tableView) 381 | { 382 | return 1; 383 | } 384 | 385 | public override int RowsInSection (UITableView tableview, int section) 386 | { 387 | if (Columns <= 0) 388 | return 4; 389 | int numRows = (int)Math.Ceiling ((float)ElcAssets.Count / Columns); 390 | return numRows; 391 | } 392 | 393 | List AssetsForIndexPath (NSIndexPath path) 394 | { 395 | int index = path.Row * Columns; 396 | int length = Math.Min (Columns, ElcAssets.Count - index); 397 | return ElcAssets.GetRange (index, length); 398 | } 399 | 400 | public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath) 401 | { 402 | const string cellIdentifier = "Cell"; 403 | 404 | var cell = TableView.DequeueReusableCell (cellIdentifier) as ELCAssetCell; 405 | if (cell == null) { 406 | cell = new ELCAssetCell (UITableViewCellStyle.Default, cellIdentifier); 407 | } 408 | cell.SetAssets (AssetsForIndexPath (indexPath), Columns); 409 | return cell; 410 | } 411 | 412 | public override float GetHeightForRow (UITableView tableView, NSIndexPath indexPath) 413 | { 414 | return 79; 415 | } 416 | 417 | public int TotalSelectedAssets; 418 | 419 | public class ELCAsset 420 | { 421 | public readonly ALAsset Asset; 422 | readonly WeakReference _Parent; 423 | 424 | bool _Selected; 425 | 426 | public ELCAsset (ELCAssetTablePicker parent, ALAsset asset) 427 | { 428 | _Parent = new WeakReference (parent); 429 | Asset = asset; 430 | } 431 | 432 | public void ToggleSelected () 433 | { 434 | Selected = !Selected; 435 | } 436 | 437 | public bool Selected { 438 | get { 439 | return _Selected; 440 | } 441 | 442 | set { 443 | var parent = _Parent.Target as ELCAssetTablePicker; 444 | if (value && parent != null && !parent.ShouldSelectAsset (this)) { 445 | return; 446 | } 447 | 448 | _Selected = value; 449 | 450 | if (parent != null) { 451 | parent.AssetSelected (this, value); 452 | } 453 | } 454 | } 455 | } 456 | 457 | class ELCAssetCell : UITableViewCell 458 | { 459 | List RowAssets; 460 | int Columns; 461 | readonly List ImageViewArray = new List (); 462 | readonly List OverlayViewArray = new List (); 463 | 464 | public ELCAssetCell (UITableViewCellStyle style, string reuseIdentifier) : base (style, reuseIdentifier) 465 | { 466 | UITapGestureRecognizer tapRecognizer = new UITapGestureRecognizer (CellTapped); 467 | AddGestureRecognizer (tapRecognizer); 468 | 469 | } 470 | 471 | public void SetAssets (List assets, int columns) 472 | { 473 | RowAssets = assets; 474 | Columns = columns; 475 | 476 | foreach (var view in ImageViewArray) { 477 | view.RemoveFromSuperview (); 478 | } 479 | foreach (var view in OverlayViewArray) { 480 | view.RemoveFromSuperview (); 481 | } 482 | 483 | UIImage overlayImage = null; 484 | for (int i = 0; i < RowAssets.Count; i++) { 485 | var asset = RowAssets [i]; 486 | 487 | if (i < ImageViewArray.Count) { 488 | var imageView = ImageViewArray [i]; 489 | imageView.Image = new UIImage (asset.Asset.Thumbnail); 490 | } else { 491 | var imageView = new UIImageView (new UIImage (asset.Asset.Thumbnail)); 492 | ImageViewArray.Add (imageView); 493 | } 494 | 495 | if (i < OverlayViewArray.Count) { 496 | var overlayView = OverlayViewArray [i]; 497 | overlayView.Hidden = !asset.Selected; 498 | } else { 499 | if (overlayImage == null) { 500 | overlayImage = new UIImage ("Overlay.png"); 501 | } 502 | var overlayView = new UIImageView (overlayImage); 503 | OverlayViewArray.Add (overlayView); 504 | overlayView.Hidden = !asset.Selected; 505 | } 506 | } 507 | } 508 | 509 | void CellTapped (UITapGestureRecognizer tapRecognizer) 510 | { 511 | PointF point = tapRecognizer.LocationInView (this); 512 | var totalWidth = Columns * 75 + (Columns - 1) * 4; 513 | var startX = (Bounds.Size.Width - totalWidth) / 2; 514 | 515 | var frame = new RectangleF (startX, 2, 75, 75); 516 | for (int i = 0; i < RowAssets.Count; ++i) { 517 | if (frame.Contains (point)) { 518 | ELCAsset asset = RowAssets [i]; 519 | asset.Selected = !asset.Selected; 520 | var overlayView = OverlayViewArray [i]; 521 | overlayView.Hidden = !asset.Selected; 522 | break; 523 | } 524 | var x = frame.X + frame.Width + 4; 525 | frame = new RectangleF (x, frame.Y, frame.Width, frame.Height); 526 | } 527 | } 528 | 529 | public override void LayoutSubviews () 530 | { 531 | var totalWidth = Columns * 75 + (Columns - 1) * 4; 532 | var startX = (Bounds.Size.Width - totalWidth) / 2; 533 | 534 | var frame = new RectangleF (startX, 2, 75, 75); 535 | 536 | int i = 0; 537 | foreach (var imageView in ImageViewArray) { 538 | imageView.Frame = frame; 539 | AddSubview (imageView); 540 | 541 | var overlayView = OverlayViewArray [i++]; 542 | overlayView.Frame = frame; 543 | AddSubview (overlayView); 544 | 545 | var x = frame.X + frame.Width + 4; 546 | frame = new RectangleF (x, frame.Y, frame.Width, frame.Height); 547 | } 548 | } 549 | } 550 | } 551 | } 552 | } -------------------------------------------------------------------------------- /iOS/ELCImagePicker/Resources/Overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bjdodson/XamarinSharpPlus/df5dc99e72b895903ff520a465958614c773f874/iOS/ELCImagePicker/Resources/Overlay.png -------------------------------------------------------------------------------- /iOS/ELCImagePicker/Resources/Overlay@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bjdodson/XamarinSharpPlus/df5dc99e72b895903ff520a465958614c773f874/iOS/ELCImagePicker/Resources/Overlay@2x.png -------------------------------------------------------------------------------- /iOS/Toast/Toast.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MonoTouch.UIKit; 3 | using System.Drawing; 4 | using System.Threading; 5 | using MonoTouch.ObjCRuntime; 6 | using System.Runtime.InteropServices; 7 | using MonoTouch.Foundation; 8 | 9 | namespace Core.Util 10 | { 11 | 12 | /// 13 | /// iOS "Toast" library, ported from: 14 | /// https://github.com/scalessec/toast 15 | /// 16 | public static class Toast 17 | { 18 | /* 19 | * CONFIGURE THESE VALUES TO ADJUST LOOK & FEEL, 20 | * DISPLAY DURATION, ETC. 21 | */ 22 | // general appearance 23 | const float CSToastMaxWidth = 0.8f; // 80% of parent view width 24 | const float CSToastMaxHeight = 0.8f; // 80% of parent view height 25 | const float CSToastHorizontalPadding = 10.0f; 26 | const float CSToastVerticalPadding = 10.0f; 27 | const float CSToastCornerRadius = 10.0f; 28 | const float CSToastOpacity = 0.8f; 29 | const float CSToastFontSize = 16.0f; 30 | const int CSToastMaxTitleLines = 0; 31 | const int CSToastMaxMessageLines = 0; 32 | const double CSToastFadeDuration = 0.3; 33 | 34 | // shadow appearance 35 | const float CSToastShadowOpacity = 0.8f; 36 | const float CSToastShadowRadius = 6.0f; 37 | static readonly SizeF CSToastShadowOffset = new SizeF (4.0f, 4.0f); 38 | const bool CSToastDisplayShadow = true; 39 | 40 | // display duration and position 41 | const string CSToastDefaultPosition = "bottom"; 42 | const double CSToastDefaultDuration = 3.5; 43 | 44 | // image view size 45 | const float CSToastImageViewWidth = 80.0f; 46 | const float CSToastImageViewHeight = 80.0f; 47 | 48 | // activity 49 | const float CSToastActivityWidth = 100.0f; 50 | const float CSToastActivityHeight = 100.0f; 51 | const string CSToastActivityDefaultPosition = "center"; 52 | 53 | 54 | // interaction 55 | const bool CSToastHidesOnTap = true; // excludes activity views 56 | 57 | // associative reference keys 58 | const string CSToastTimerKey = "CSToastTimerKey"; 59 | const string CSToastActivityViewKey = "CSToastActivityViewKey"; 60 | 61 | #region Toast Methods 62 | 63 | public static void MakeToast (this UIView context, string message) 64 | { 65 | MakeToast (context, message, CSToastDefaultDuration, CSToastDefaultPosition, null, null); 66 | } 67 | 68 | public static void MakeToast (this UIView context, string message, double duration, object position, string title, UIImage image) 69 | { 70 | UIView toast = ViewForMessage (context, message, title, image); 71 | ShowToast (context, toast, duration, position); 72 | } 73 | 74 | public static void ShowToast (this UIView context, UIView toast) 75 | { 76 | ShowToast (context, toast, CSToastDefaultDuration, CSToastDefaultPosition); 77 | } 78 | 79 | public static void ShowToast (this UIView context, UIView toast, double duration, object point) 80 | { 81 | toast.Center = CenterPointForPosition (context, point, toast); 82 | toast.Alpha = 0.0f; 83 | 84 | if (CSToastHidesOnTap) { 85 | UITapGestureRecognizer recognizer = new UITapGestureRecognizer (() => HandleToastTapped (toast)); 86 | toast.AddGestureRecognizer (recognizer); 87 | toast.UserInteractionEnabled = true; 88 | toast.ExclusiveTouch = true; 89 | } 90 | 91 | context.AddSubview (toast); 92 | UIView.Animate (CSToastFadeDuration, 0.0, UIViewAnimationOptions.CurveEaseOut | UIViewAnimationOptions.AllowUserInteraction, 93 | () => toast.Alpha = 1.0f, 94 | () => { 95 | Timer timer = new Timer (t => ToastTimerDidFinish (toast)); 96 | timer.Change ((int)(duration*1000), Timeout.Infinite); 97 | } 98 | ); 99 | } 100 | 101 | static void HideToast (UIView toast) 102 | { 103 | if (toast.Superview == null) 104 | return; 105 | UIView.Animate (CSToastFadeDuration, 0.0, UIViewAnimationOptions.CurveEaseIn | UIViewAnimationOptions.BeginFromCurrentState, 106 | () => toast.Alpha = 0.0f, toast.RemoveFromSuperview); 107 | } 108 | 109 | #endregion 110 | 111 | #region Events 112 | 113 | static void HandleToastTapped (UIView toast) 114 | { 115 | HideToast (toast); 116 | } 117 | 118 | static void ToastTimerDidFinish (UIView toast) 119 | { 120 | new DispatchAdapter ().Invoke (() => HideToast (toast)); 121 | } 122 | 123 | #endregion 124 | 125 | #region Toast Activity Methods 126 | 127 | static UIView _ExistingActivityView; 128 | 129 | public static void MakeToastActivity (this UIView context) 130 | { 131 | MakeToastActivity (context, CSToastActivityDefaultPosition); 132 | } 133 | 134 | public static void MakeToastActivity (this UIView context, object position) 135 | { 136 | // sanity 137 | if (_ExistingActivityView != null) 138 | return; 139 | 140 | var activityView = new UIView (new RectangleF (0f, 0f, CSToastActivityWidth, CSToastActivityHeight)); 141 | activityView.Center = CenterPointForPosition (context, position, activityView); 142 | activityView.BackgroundColor = UIColor.Black.ColorWithAlpha (CSToastOpacity); 143 | activityView.Alpha = 0f; 144 | activityView.AutoresizingMask = UIViewAutoresizing.FlexibleLeftMargin | UIViewAutoresizing.FlexibleRightMargin | UIViewAutoresizing.FlexibleTopMargin | UIViewAutoresizing.FlexibleBottomMargin; 145 | activityView.Layer.CornerRadius = CSToastCornerRadius; 146 | if (CSToastDisplayShadow) { 147 | activityView.Layer.ShadowColor = UIColor.Black.CGColor; 148 | activityView.Layer.ShadowOpacity = CSToastShadowOpacity; 149 | activityView.Layer.ShadowRadius = CSToastShadowRadius; 150 | activityView.Layer.ShadowOffset = CSToastShadowOffset; 151 | } 152 | 153 | UIActivityIndicatorView activityIndicatorView = new UIActivityIndicatorView (UIActivityIndicatorViewStyle.WhiteLarge); 154 | activityIndicatorView.Center = new PointF (activityView.Bounds.Size.Width/2, activityView.Bounds.Size.Height/2); 155 | activityView.AddSubview (activityIndicatorView); 156 | activityIndicatorView.StartAnimating (); 157 | 158 | // associate the activity view with self 159 | _ExistingActivityView = activityView; 160 | context.AddSubview (activityView); 161 | 162 | UIView.Animate (CSToastFadeDuration, 0.0, UIViewAnimationOptions.CurveEaseOut, 163 | () => activityView.Alpha = 1.0f, 164 | null); 165 | } 166 | 167 | public static void HideToastActivity (this UIView context) 168 | { 169 | if (_ExistingActivityView != null) { 170 | UIView.Animate (CSToastFadeDuration, 0.0f, UIViewAnimationOptions.CurveEaseIn | UIViewAnimationOptions.BeginFromCurrentState, 171 | () => _ExistingActivityView.Alpha = 0.0f, 172 | () => { 173 | _ExistingActivityView.RemoveFromSuperview (); 174 | _ExistingActivityView = null; 175 | } 176 | ); 177 | } 178 | } 179 | 180 | #endregion 181 | 182 | #region Helpers 183 | 184 | static PointF CenterPointForPosition (UIView view, object point, UIView toast) 185 | { 186 | if (point is string) { 187 | // convert string literals @"top", @"bottom", @"center", or any point wrapped in an NSValue object into a CGPoint 188 | if (String.Equals ((string)point, "top", StringComparison.OrdinalIgnoreCase)) { 189 | return new PointF (view.Bounds.Width / 2, (view.Bounds.Height / 2) + CSToastVerticalPadding); 190 | } else if (String.Equals ((string)point, "bottom", StringComparison.OrdinalIgnoreCase)) { 191 | return new PointF (view.Bounds.Width / 2, (view.Bounds.Size.Height - (toast.Frame.Height / 2)) - CSToastVerticalPadding); 192 | } else if (String.Equals ((string)point, "center", StringComparison.OrdinalIgnoreCase)) { 193 | return new PointF (view.Bounds.Width / 2, view.Bounds.Height / 2); 194 | } 195 | } else if (point is PointF) { 196 | return (PointF)point; 197 | } 198 | 199 | Logger.WriteLine ("Warning: Invalid position for toast."); 200 | return CenterPointForPosition (view, CSToastDefaultPosition, toast); 201 | } 202 | 203 | static SizeF SizeForString (string message, UIFont font, SizeF size, UILineBreakMode lineBreakMode) 204 | { 205 | 206 | /*if ([string respondsToSelector:@selector(boundingRectWithSize:options:attributes:context:)]) { 207 | NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init]; 208 | paragraphStyle.lineBreakMode = lineBreakMode; 209 | NSDictionary *attributes = @{NSFontAttributeName:font, NSParagraphStyleAttributeName:paragraphStyle}; 210 | CGRect boundingRect = [string boundingRectWithSize:constrainedSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil]; 211 | return CGSizeMake(ceilf(boundingRect.size.width), ceilf(boundingRect.size.height)); 212 | } 213 | 214 | return [string sizeWithFont:font constrainedToSize:constrainedSize lineBreakMode:lineBreakMode];*/ 215 | 216 | return TextUtils.SizeWithFont (message, font, size, lineBreakMode); 217 | } 218 | 219 | static UIView ViewForMessage (UIView context, string message, string title, UIImage image) 220 | { 221 | // sanity 222 | if ((message == null) && (title == null) && (image == null)) return null; 223 | 224 | // dynamically build a toast view with any combination of message, title, & image. 225 | UILabel messageLabel = null; 226 | UILabel titleLabel = null; 227 | UIImageView imageView = null; 228 | 229 | // create the parent view 230 | UIView wrapperView = new UIView (); 231 | wrapperView.AutoresizingMask = UIViewAutoresizing.FlexibleLeftMargin | UIViewAutoresizing.FlexibleRightMargin | UIViewAutoresizing.FlexibleTopMargin | UIViewAutoresizing.FlexibleBottomMargin; 232 | wrapperView.Layer.CornerRadius = CSToastCornerRadius; 233 | 234 | if (CSToastDisplayShadow) { 235 | wrapperView.Layer.ShadowColor = UIColor.Black.CGColor; 236 | wrapperView.Layer.ShadowOpacity = CSToastShadowOpacity; 237 | wrapperView.Layer.ShadowRadius = CSToastShadowRadius; 238 | wrapperView.Layer.ShadowOffset = CSToastShadowOffset; 239 | } 240 | 241 | wrapperView.BackgroundColor = UIColor.Black.ColorWithAlpha (CSToastOpacity); 242 | if (image != null) { 243 | imageView = new UIImageView (image); 244 | imageView.ContentMode = UIViewContentMode.ScaleAspectFit; 245 | imageView.Frame = new RectangleF (CSToastHorizontalPadding, CSToastVerticalPadding, CSToastImageViewWidth, CSToastImageViewHeight); 246 | } 247 | 248 | float imageWidth, imageHeight, imageLeft; 249 | if (imageView != null) { 250 | imageWidth = imageView.Bounds.Width; 251 | imageHeight = imageView.Bounds.Height; 252 | imageLeft = CSToastHorizontalPadding; 253 | } else { 254 | imageWidth = imageHeight = imageLeft = 0.0f; 255 | } 256 | 257 | if (title != null) { 258 | titleLabel = new UILabel (); 259 | titleLabel.Lines = CSToastMaxTitleLines; 260 | titleLabel.Font = UIFont.BoldSystemFontOfSize (CSToastFontSize); 261 | titleLabel.TextAlignment = UITextAlignment.Left; 262 | titleLabel.LineBreakMode = UILineBreakMode.WordWrap; 263 | titleLabel.TextColor = UIColor.White; 264 | titleLabel.BackgroundColor = UIColor.Clear; 265 | titleLabel.Alpha = 1.0f; 266 | titleLabel.Text = title; 267 | 268 | // size the title label according to the length of the text 269 | SizeF maxSizeTitle = new SizeF ((context.Bounds.Size.Width * CSToastMaxWidth) - imageWidth, context.Bounds.Size.Height * CSToastMaxHeight); 270 | SizeF expectedSizeTitle = SizeForString (title, titleLabel.Font, maxSizeTitle, titleLabel.LineBreakMode); 271 | titleLabel.Frame = new RectangleF (0.0f, 0.0f, expectedSizeTitle.Width, expectedSizeTitle.Height); 272 | } 273 | 274 | if (message != null) { 275 | messageLabel = new UILabel (); 276 | messageLabel.Lines = CSToastMaxMessageLines; 277 | messageLabel.Font = UIFont.SystemFontOfSize (CSToastFontSize); 278 | messageLabel.LineBreakMode = UILineBreakMode.WordWrap; 279 | messageLabel.TextColor = UIColor.White; 280 | messageLabel.BackgroundColor = UIColor.Clear; 281 | messageLabel.Alpha = 1.0f; 282 | messageLabel.Text = message; 283 | 284 | // size the message label according to the length of the text 285 | SizeF maxSizeMessage = new SizeF((context.Bounds.Size.Width * CSToastMaxWidth) - imageWidth, context.Bounds.Size.Height * CSToastMaxHeight); 286 | SizeF expectedSizeMessage = SizeForString (message, messageLabel.Font, maxSizeMessage, messageLabel.LineBreakMode); 287 | messageLabel.Frame = new RectangleF (0.0f, 0.0f, expectedSizeMessage.Width, expectedSizeMessage.Height); 288 | } 289 | 290 | // titleLabel frame values 291 | float titleWidth, titleHeight, titleTop, titleLeft; 292 | 293 | if (titleLabel != null) { 294 | titleWidth = titleLabel.Bounds.Size.Width; 295 | titleHeight = titleLabel.Bounds.Size.Height; 296 | titleTop = CSToastVerticalPadding; 297 | titleLeft = imageLeft + imageWidth + CSToastHorizontalPadding; 298 | } else { 299 | titleWidth = titleHeight = titleTop = titleLeft = 0.0f; 300 | } 301 | 302 | // messageLabel frame values 303 | float messageWidth, messageHeight, messageLeft, messageTop; 304 | 305 | if (messageLabel != null) { 306 | messageWidth = messageLabel.Bounds.Size.Width; 307 | messageHeight = messageLabel.Bounds.Size.Height; 308 | messageLeft = imageLeft + imageWidth + CSToastHorizontalPadding; 309 | messageTop = titleTop + titleHeight + CSToastVerticalPadding; 310 | } else { 311 | messageWidth = messageHeight = messageLeft = messageTop = 0.0f; 312 | } 313 | 314 | float longerWidth = Math.Max (titleWidth, messageWidth); 315 | float longerLeft = Math.Max (titleLeft, messageLeft); 316 | 317 | // wrapper width uses the longerWidth or the image width, whatever is larger. same logic applies to the wrapper height 318 | float wrapperWidth = Math.Max((imageWidth + (CSToastHorizontalPadding * 2)), (longerLeft + longerWidth + CSToastHorizontalPadding)); 319 | float wrapperHeight = Math.Max((messageTop + messageHeight + CSToastVerticalPadding), (imageHeight + (CSToastVerticalPadding * 2))); 320 | 321 | wrapperView.Frame = new RectangleF(0.0f, 0.0f, wrapperWidth, wrapperHeight); 322 | 323 | if (titleLabel != null) { 324 | titleLabel.Frame = new RectangleF (titleLeft, titleTop, titleWidth, titleHeight); 325 | wrapperView.AddSubview (titleLabel); 326 | } 327 | 328 | if (messageLabel != null) { 329 | messageLabel.Frame = new RectangleF(messageLeft, messageTop, messageWidth, messageHeight); 330 | wrapperView.AddSubview (messageLabel); 331 | } 332 | 333 | if (imageView != null) { 334 | wrapperView.AddSubview (imageView); 335 | } 336 | 337 | return wrapperView; 338 | } 339 | 340 | #endregion 341 | } 342 | } --------------------------------------------------------------------------------