1
0
mirror of https://github.com/bitwarden/mobile synced 2026-01-03 17:13:50 +00:00

[Auto Logout] Final review of feature (#932)

* Initial commit of LockService name refactor (#831)

* [Auto-Logout] Update Service layer logic (#835)

* Initial commit of service logic update

* Added default value for action

* Updated ToggleTokensAsync conditional

* Removed unused variables, updated action conditional

* Initial commit: lockOption/lock refactor app layer (#840)

* [Auto-Logout] Settings Refactor - Application Layer Part 2 (#844)

* Initial commit of app layer part 2

* Updated biometrics position

* Reverted resource name refactor

* LockOptions refactor revert

* Updated method casing :: Removed VaultTimeout prefix for timeouts

* Fixed dupe string resource (#854)

* Updated dependency to use VaultTimeoutService (#896)

* [Auto Logout] Xamarin Forms in AutoFill flow (iOS) (#902)

* fix typo in PINRequireMasterPasswordRestart (#900)

* initial commit for xf usage in autofill

* Fixed databinding for hint button

* Updated Two Factor page launch - removed unused imports

* First pass at broadcast/messenger implentation for autofill

* setting theme in extension using theme manager

* extension app resources

* App resources from main app

* fix ref to twoFactorPage

* apply resources to page

* load empty app for sytling in extension

* move ios renderers to ios core

* static ref to resources and GetResourceColor helper

* fix method ref

* move application.current.resources refs to helper

* switch login page alerts to device action dialogs

* run on main thread

* showDialog with device action service

* abstract action sheet to device action service

* add support for yubikey

* add yubikey iimages to extension

* support close button action

* add support to action extension

* remove empty lines

Co-authored-by: Jonas Kittner <54631600+theendlessriver13@users.noreply.github.com>
Co-authored-by: Kyle Spearrin <kyle.spearrin@gmail.com>

* [Auto Logout] Update lock option to be default value (#929)

* Initial commit - make lock action default

* Removed extra whitespace

Co-authored-by: Jonas Kittner <54631600+theendlessriver13@users.noreply.github.com>
Co-authored-by: Kyle Spearrin <kyle.spearrin@gmail.com>
Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
This commit is contained in:
Vincent Salucci
2020-05-29 11:26:36 -05:00
committed by GitHub
parent 39e10ff01c
commit 4c3df2e1e1
80 changed files with 744 additions and 379 deletions

View File

@@ -26,10 +26,10 @@ namespace Bit.iOS
{
private NFCNdefReaderSession _nfcSession = null;
private iOSPushNotificationHandler _pushHandler = null;
private NFCReaderDelegate _nfcDelegate = null;
private Core.NFCReaderDelegate _nfcDelegate = null;
private NSTimer _clipboardTimer = null;
private nint _clipboardBackgroundTaskId;
private NSTimer _lockTimer = null;
private NSTimer _vaultTimeoutTimer = null;
private nint _lockBackgroundTaskId;
private NSTimer _eventTimer = null;
private nint _eventBackgroundTaskId;
@@ -38,7 +38,7 @@ namespace Bit.iOS
private IMessagingService _messagingService;
private IBroadcasterService _broadcasterService;
private IStorageService _storageService;
private ILockService _lockService;
private IVaultTimeoutService _vaultTimeoutService;
private IEventService _eventService;
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
@@ -50,7 +50,7 @@ namespace Bit.iOS
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_lockService = ServiceContainer.Resolve<ILockService>("lockService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
LoadApplication(new App.App(null));
@@ -59,13 +59,13 @@ namespace Bit.iOS
_broadcasterService.Subscribe(nameof(AppDelegate), async (message) =>
{
if (message.Command == "scheduleLockTimer")
if (message.Command == "scheduleVaultTimeoutTimer")
{
LockTimer((int)message.Data);
VaultTimeoutTimer((int)message.Data);
}
else if (message.Command == "cancelLockTimer")
else if (message.Command == "cancelVaultTimeoutTimer")
{
CancelLockTimer();
CancelVaultTimeoutTimer();
}
else if (message.Command == "startEventTimer")
{
@@ -89,7 +89,7 @@ namespace Bit.iOS
}
else if (message.Command == "listenYubiKeyOTP")
{
ListenYubiKey((bool)message.Data);
iOSCoreHelpers.ListenYubiKey((bool)message.Data, _deviceActionService, _nfcSession, _nfcDelegate);
}
else if (message.Command == "unlocked")
{
@@ -186,8 +186,7 @@ namespace Bit.iOS
};
var backgroundView = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
{
BackgroundColor = ((Color)Xamarin.Forms.Application.Current.Resources["SplashBackgroundColor"])
.ToUIColor()
BackgroundColor = ThemeManager.GetResourceColor("SplashBackgroundColor").ToUIColor()
};
var logo = new UIImage(!ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png");
var imageView = new UIImageView(logo)
@@ -284,7 +283,7 @@ namespace Bit.iOS
iOSCoreHelpers.RegisterAppCenter();
_pushHandler = new iOSPushNotificationHandler(
ServiceContainer.Resolve<IPushNotificationListenerService>("pushNotificationListenerService"));
_nfcDelegate = new NFCReaderDelegate((success, message) =>
_nfcDelegate = new Core.NFCReaderDelegate((success, message) =>
_messagingService.Send("gotYubiKeyOTP", message));
iOSCoreHelpers.Bootstrap(async () => await ApplyManagedSettingsAsync());
@@ -300,25 +299,7 @@ namespace Bit.iOS
"pushNotificationService", iosPushNotificationService);
}
private void ListenYubiKey(bool listen)
{
if (_deviceActionService.SupportsNfc())
{
_nfcSession?.InvalidateSession();
_nfcSession?.Dispose();
_nfcSession = null;
if (listen)
{
_nfcSession = new NFCNdefReaderSession(_nfcDelegate, null, true)
{
AlertMessage = AppResources.HoldYubikeyNearTop
};
_nfcSession.BeginSession();
}
}
}
private void LockTimer(int lockOptionMinutes)
private void VaultTimeoutTimer(int vaultTimeoutMinutes)
{
if (_lockBackgroundTaskId > 0)
{
@@ -330,21 +311,21 @@ namespace Bit.iOS
UIApplication.SharedApplication.EndBackgroundTask(_lockBackgroundTaskId);
_lockBackgroundTaskId = 0;
});
var lockOptionMs = lockOptionMinutes * 60000;
_lockTimer?.Invalidate();
_lockTimer?.Dispose();
_lockTimer = null;
var lockMsSpan = TimeSpan.FromMilliseconds(lockOptionMs + 10);
var vaultTimeoutMs = vaultTimeoutMinutes * 60000;
_vaultTimeoutTimer?.Invalidate();
_vaultTimeoutTimer?.Dispose();
_vaultTimeoutTimer = null;
var vaultTimeoutMsSpan = TimeSpan.FromMilliseconds(vaultTimeoutMs + 10);
Device.BeginInvokeOnMainThread(() =>
{
_lockTimer = NSTimer.CreateScheduledTimer(lockMsSpan, timer =>
_vaultTimeoutTimer = NSTimer.CreateScheduledTimer(vaultTimeoutMsSpan, timer =>
{
Device.BeginInvokeOnMainThread(() =>
{
_lockService.CheckLockAsync();
_lockTimer?.Invalidate();
_lockTimer?.Dispose();
_lockTimer = null;
_vaultTimeoutService.CheckVaultTimeoutAsync();
_vaultTimeoutTimer?.Invalidate();
_vaultTimeoutTimer?.Dispose();
_vaultTimeoutTimer = null;
if (_lockBackgroundTaskId > 0)
{
UIApplication.SharedApplication.EndBackgroundTask(_lockBackgroundTaskId);
@@ -355,11 +336,11 @@ namespace Bit.iOS
});
}
private void CancelLockTimer()
private void CancelVaultTimeoutTimer()
{
_lockTimer?.Invalidate();
_lockTimer?.Dispose();
_lockTimer = null;
_vaultTimeoutTimer?.Invalidate();
_vaultTimeoutTimer?.Dispose();
_vaultTimeoutTimer = null;
if (_lockBackgroundTaskId > 0)
{
UIApplication.SharedApplication.EndBackgroundTask(_lockBackgroundTaskId);

View File

@@ -1,53 +0,0 @@
using CoreNFC;
using Foundation;
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace Bit.iOS
{
public class NFCReaderDelegate : NFCNdefReaderSessionDelegate
{
private Regex _otpPattern = new Regex("^.*?([cbdefghijklnrtuv]{32,64})$");
private Action<bool, string> _callback;
public NFCReaderDelegate(Action<bool, string> callback)
{
_callback = callback;
}
public override void DidDetect(NFCNdefReaderSession session, NFCNdefMessage[] messages)
{
var results = new List<string>();
foreach (var message in messages)
{
foreach (var record in message.Records)
{
try
{
results.Add(new NSString(record.Payload, NSStringEncoding.UTF8));
}
catch { }
}
}
foreach (var result in results)
{
var matches = _otpPattern.Matches(result);
if (matches.Count > 0 && matches[0].Groups.Count > 1)
{
var otp = matches[0].Groups[1].ToString();
_callback.Invoke(true, otp);
return;
}
}
_callback.Invoke(false, "No tags were read.");
}
public override void DidInvalidate(NFCNdefReaderSession session, NSError error)
{
_callback.Invoke(false, error?.LocalizedDescription);
}
}
}

View File

@@ -1,40 +0,0 @@
using System.ComponentModel;
using Bit.iOS.Renderers;
using Bit.iOS.Utilities;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(Button), typeof(CustomButtonRenderer))]
namespace Bit.iOS.Renderers
{
public class CustomButtonRenderer : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (Control != null && e.NewElement is Button)
{
UpdateFont();
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Button.FontProperty.PropertyName)
{
UpdateFont();
}
}
private void UpdateFont()
{
var pointSize = iOSHelpers.GetAccessibleFont<Button>(Element.FontSize);
if (pointSize != null)
{
Control.Font = UIFont.FromDescriptor(Element.Font.ToUIFont().FontDescriptor, pointSize.Value);
}
}
}
}

View File

@@ -1,63 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Bit.iOS.Renderers;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(ContentPage), typeof(CustomContentPageRenderer))]
namespace Bit.iOS.Renderers
{
public class CustomContentPageRenderer : PageRenderer
{
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
if (!(Element is ContentPage contentPage) || NavigationController == null)
{
return;
}
// Hide bottom line under nav bar
var navBar = NavigationController.NavigationBar;
if (navBar != null)
{
navBar.SetValueForKey(FromObject(true), new Foundation.NSString("hidesShadow"));
}
var navigationItem = NavigationController.TopViewController.NavigationItem;
var leftNativeButtons = (navigationItem.LeftBarButtonItems ?? new UIBarButtonItem[] { }).ToList();
var rightNativeButtons = (navigationItem.RightBarButtonItems ?? new UIBarButtonItem[] { }).ToList();
var newLeftButtons = new List<UIBarButtonItem>();
var newRightButtons = new List<UIBarButtonItem>();
foreach (var nativeItem in rightNativeButtons)
{
// Use reflection to get Xamarin private field "_item"
var field = nativeItem.GetType().GetField("_item", BindingFlags.NonPublic | BindingFlags.Instance);
if (field == null)
{
return;
}
if (!(field.GetValue(nativeItem) is ToolbarItem info))
{
return;
}
if (info.Priority < 0)
{
newLeftButtons.Add(nativeItem);
}
else
{
newRightButtons.Add(nativeItem);
}
}
foreach (var nativeItem in leftNativeButtons)
{
newLeftButtons.Add(nativeItem);
}
navigationItem.RightBarButtonItems = newRightButtons.ToArray();
navigationItem.LeftBarButtonItems = newLeftButtons.ToArray();
}
}
}

View File

@@ -1,49 +0,0 @@
using Bit.iOS.Renderers;
using System.ComponentModel;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(Editor), typeof(CustomEditorRenderer))]
namespace Bit.iOS.Renderers
{
public class CustomEditorRenderer : EditorRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
base.OnElementChanged(e);
if (e.NewElement is Editor)
{
var descriptor = UIFontDescriptor.PreferredBody;
Control.Font = UIFont.FromDescriptor(descriptor, descriptor.PointSize);
// Remove padding
Control.TextContainerInset = new UIEdgeInsets(0, 0, 0, 0);
Control.TextContainer.LineFragmentPadding = 0;
UpdateTintColor();
UpdateKeyboardAppearance();
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Editor.TextColorProperty.PropertyName)
{
UpdateTintColor();
}
}
private void UpdateTintColor()
{
Control.TintColor = Element.TextColor.ToUIColor();
}
private void UpdateKeyboardAppearance()
{
if (!Core.Utilities.ThemeHelpers.LightTheme)
{
Control.KeyboardAppearance = UIKeyboardAppearance.Dark;
}
}
}
}

View File

@@ -1,70 +0,0 @@
using System.ComponentModel;
using Bit.iOS.Renderers;
using Bit.iOS.Utilities;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(Entry), typeof(CustomEntryRenderer))]
namespace Bit.iOS.Renderers
{
public class CustomEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null && e.NewElement is Entry)
{
Control.ClearButtonMode = UITextFieldViewMode.WhileEditing;
UpdateTintColor();
UpdateFontSize();
UpdateKeyboardAppearance();
iOSHelpers.SetBottomBorder(Control);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Entry.FontAttributesProperty.PropertyName ||
e.PropertyName == Entry.FontFamilyProperty.PropertyName ||
e.PropertyName == Entry.FontSizeProperty.PropertyName)
{
UpdateFontSize();
}
else if (e.PropertyName == Entry.TextColorProperty.PropertyName)
{
UpdateTintColor();
}
}
private void UpdateFontSize()
{
var pointSize = iOSHelpers.GetAccessibleFont<Entry>(Element.FontSize);
if (pointSize != null)
{
if (!string.IsNullOrWhiteSpace(Element.FontFamily))
{
Control.Font = UIFont.FromName(Element.FontFamily, pointSize.Value);
}
else
{
Control.Font = UIFont.FromDescriptor(UIFontDescriptor.PreferredBody, pointSize.Value);
}
}
}
private void UpdateTintColor()
{
Control.TintColor = Element.TextColor.ToUIColor();
}
private void UpdateKeyboardAppearance()
{
if (!Core.Utilities.ThemeHelpers.LightTheme)
{
Control.KeyboardAppearance = UIKeyboardAppearance.Dark;
}
}
}
}

View File

@@ -1,42 +0,0 @@
using System.ComponentModel;
using Bit.iOS.Renderers;
using Bit.iOS.Utilities;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(Label), typeof(CustomLabelRenderer))]
namespace Bit.iOS.Renderers
{
public class CustomLabelRenderer : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Control != null && e.NewElement is Label)
{
UpdateFont();
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Label.FontProperty.PropertyName ||
e.PropertyName == Label.TextProperty.PropertyName ||
e.PropertyName == Label.FormattedTextProperty.PropertyName)
{
UpdateFont();
}
}
private void UpdateFont()
{
var pointSize = iOSHelpers.GetAccessibleFont<Label>(Element.FontSize);
if (pointSize != null)
{
Control.Font = UIFont.FromDescriptor(Element.Font.ToUIFont().FontDescriptor, pointSize.Value);
}
}
}
}

View File

@@ -1,32 +0,0 @@
using Bit.iOS.Renderers;
using Bit.iOS.Utilities;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(Picker), typeof(CustomPickerRenderer))]
namespace Bit.iOS.Renderers
{
public class CustomPickerRenderer : PickerRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if (e.NewElement is Picker)
{
var descriptor = UIFontDescriptor.PreferredBody;
Control.Font = UIFont.FromDescriptor(descriptor, descriptor.PointSize);
iOSHelpers.SetBottomBorder(Control);
UpdateKeyboardAppearance();
}
}
private void UpdateKeyboardAppearance()
{
if (!Core.Utilities.ThemeHelpers.LightTheme)
{
Control.KeyboardAppearance = UIKeyboardAppearance.Dark;
}
}
}
}

View File

@@ -1,28 +0,0 @@
using Bit.iOS.Renderers;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(SearchBar), typeof(CustomSearchBarRenderer))]
namespace Bit.iOS.Renderers
{
public class CustomSearchBarRenderer : SearchBarRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
{
base.OnElementChanged(e);
if (e.NewElement is SearchBar)
{
UpdateKeyboardAppearance();
}
}
private void UpdateKeyboardAppearance()
{
if (!Core.Utilities.ThemeHelpers.LightTheme)
{
Control.KeyboardAppearance = UIKeyboardAppearance.Dark;
}
}
}
}

View File

@@ -1,17 +0,0 @@
using Bit.iOS.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabbedRenderer))]
namespace Bit.iOS.Renderers
{
public class CustomTabbedRenderer : TabbedRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
TabBar.Translucent = false;
TabBar.Opaque = true;
}
}
}

View File

@@ -1,28 +0,0 @@
using Bit.iOS.Renderers;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(ViewCell), typeof(CustomViewCellRenderer))]
namespace Bit.iOS.Renderers
{
public class CustomViewCellRenderer : ViewCellRenderer
{
private bool _noSelectionStyle = false;
public CustomViewCellRenderer()
{
_noSelectionStyle = (Color)Xamarin.Forms.Application.Current.Resources["BackgroundColor"] != Color.White;
}
public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
{
var cell = base.GetCell(item, reusableCell, tv);
if (_noSelectionStyle)
{
cell.SelectionStyle = UITableViewCellSelectionStyle.None;
}
return cell;
}
}
}

View File

@@ -1,64 +0,0 @@
using Foundation;
using WebKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using Bit.App.Controls;
using Bit.iOS.Renderers;
using System.ComponentModel;
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace Bit.iOS.Renderers
{
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, WKWebView>, IWKScriptMessageHandler
{
private const string JSFunction =
"function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
private WKUserContentController _userController;
protected override void OnElementChanged(ElementChangedEventArgs<HybridWebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
_userController = new WKUserContentController();
var script = new WKUserScript(new NSString(JSFunction), WKUserScriptInjectionTime.AtDocumentEnd, false);
_userController.AddUserScript(script);
_userController.AddScriptMessageHandler(this, "invokeAction");
var config = new WKWebViewConfiguration { UserContentController = _userController };
var webView = new WKWebView(Frame, config);
SetNativeControl(webView);
}
if (e.OldElement != null)
{
_userController.RemoveAllUserScripts();
_userController.RemoveScriptMessageHandler("invokeAction");
var hybridWebView = e.OldElement as HybridWebView;
hybridWebView.Cleanup();
}
if (e.NewElement != null)
{
if (Element.Uri != null)
{
Control.LoadRequest(new NSUrlRequest(new NSUrl(Element.Uri)));
}
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == HybridWebView.UriProperty.PropertyName && Element.Uri != null)
{
Control.LoadRequest(new NSUrlRequest(new NSUrl(Element.Uri)));
}
}
public void DidReceiveScriptMessage(WKUserContentController userContentController, WKScriptMessage message)
{
Element.InvokeAction(message.Body.ToString());
}
}
}

View File

@@ -1,63 +0,0 @@
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
namespace Bit.iOS.Utilities
{
public static class iOSHelpers
{
public static System.nfloat? GetAccessibleFont<T>(double size)
{
var pointSize = UIFontDescriptor.PreferredBody.PointSize;
if (size == Device.GetNamedSize(NamedSize.Large, typeof(T)))
{
pointSize *= 1.3f;
}
else if (size == Device.GetNamedSize(NamedSize.Small, typeof(T)))
{
pointSize *= .8f;
}
else if (size == Device.GetNamedSize(NamedSize.Micro, typeof(T)))
{
pointSize *= .6f;
}
else if (size != Device.GetNamedSize(NamedSize.Default, typeof(T)))
{
// not using dynamic font sizes, return
return null;
}
return pointSize;
}
public static void SetBottomBorder(UITextField control)
{
control.BorderStyle = UITextBorderStyle.None;
SetBottomBorder(control as UIView);
}
public static void SetBottomBorder(UITextView control)
{
SetBottomBorder(control as UIView);
}
private static void SetBottomBorder(UIView control)
{
var borderLine = new UIView
{
BackgroundColor = ((Color)Xamarin.Forms.Application.Current.Resources["BoxBorderColor"]).ToUIColor(),
TranslatesAutoresizingMaskIntoConstraints = false
};
control.AddSubview(borderLine);
control.AddConstraints(new NSLayoutConstraint[]
{
NSLayoutConstraint.Create(borderLine, NSLayoutAttribute.Height, NSLayoutRelation.Equal, 1, 1f),
NSLayoutConstraint.Create(borderLine, NSLayoutAttribute.Leading, NSLayoutRelation.Equal,
control, NSLayoutAttribute.Leading, 1, 0),
NSLayoutConstraint.Create(borderLine, NSLayoutAttribute.Trailing, NSLayoutRelation.Equal,
control, NSLayoutAttribute.Trailing, 1, 0),
NSLayoutConstraint.Create(borderLine, NSLayoutAttribute.Top, NSLayoutRelation.Equal,
control, NSLayoutAttribute.Bottom, 1, 10f),
});
}
}
}

View File

@@ -134,20 +134,8 @@
<ItemGroup>
<Compile Include="Main.cs" />
<Compile Include="AppDelegate.cs" />
<Compile Include="NFCReaderDelegate.cs" />
<Compile Include="Renderers\CustomButtonRenderer.cs" />
<Compile Include="Renderers\CustomSearchBarRenderer.cs" />
<Compile Include="Renderers\CustomTabbedRenderer.cs" />
<Compile Include="Renderers\CustomPickerRenderer.cs" />
<Compile Include="Renderers\CustomEntryRenderer.cs" />
<Compile Include="Renderers\CustomEditorRenderer.cs" />
<Compile Include="Renderers\CustomViewCellRenderer.cs" />
<Compile Include="Renderers\CustomLabelRenderer.cs" />
<Compile Include="Renderers\CustomContentPageRenderer.cs" />
<Compile Include="Renderers\HybridWebViewRenderer.cs" />
<Compile Include="Services\iOSPushNotificationHandler.cs" />
<Compile Include="Services\iOSPushNotificationService.cs" />
<Compile Include="Utilities\iOSHelpers.cs" />
<None Include="Entitlements.plist" />
<None Include="Info.plist" />
<Compile Include="Properties\AssemblyInfo.cs" />