I develop Xamarin Forms app. And I use QLPreviewController for ios. I want to close copy text/text share, if document has text. Is it possible and how? I searched it as native. Some sources redirect me to UIDocumentInteractionControllerDelegate. But I didn't understand how can i do it.
Thanks in advance.
Edit:
My UIDocumentInteractionController implementation:
var firstController = UIApplication.SharedApplication.KeyWindow.RootViewController.ChildViewControllers.First().ChildViewControllers.Last().ChildViewControllers.First();
var navcontroller = firstController as UINavigationController;
var uidic = UIDocumentInteractionController.FromUrl(new NSUrl(file, true));
uidic.Delegate = new DocInteractionController(navcontroller);
uidic.PresentPreview(true);
public class DocInteractionController : UIDocumentInteractionControllerDelegate
{
UINavigationController navigationController;
public DocInteractionController(UINavigationController _navigationController)
{
navigationController = _navigationController;
}
}
In your case it would be better to use WebView to preview files like pdf .
In forms
Create a custom webview
public class MyWebView : WebView
{
public static readonly BindableProperty UriProperty = BindableProperty.Create(propertyName: "Uri",
returnType: typeof(string),
declaringType: typeof(MyWebView),
defaultValue: default(string));
public string Uri
{
get { return (string)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
}
in Android project
using Android.Content;
using Android.Net.Http;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Webkit;
using Android.Widget;
using App32;
using App32.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(MyWebView), typeof(CustomWebViewRenderer))]
namespace App32.Droid
{
public class CustomWebViewRenderer : WebViewRenderer
{
Context _context;
public CustomWebViewRenderer(Context context) : base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
Android.Webkit.WebView web_view = new Android.Webkit.WebView(_context);
web_view.LoadUrl("https://drive.google.com/viewerng/viewer?embedded=true&url="+((MyWebView)Element).Uri);
SetNativeControl(web_view);
Control.Settings.JavaScriptEnabled = true;
}
}
}
}
in iOS project
using System;
using App32;
using App32.iOS;
using Foundation;
using ObjCRuntime;
using UIKit;
using WebKit;
using Xamarin.Forms.Platform.iOS;
[assembly: Xamarin.Forms.ExportRenderer(typeof(MyWebView), typeof(MyWebViewRenderer))]
namespace App32.iOS
{
public class MyWebViewRenderer : ViewRenderer<MyWebView, WKWebView>
{
WKWebView _wkWebView;
protected override void OnElementChanged(ElementChangedEventArgs<MyWebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
var config = new WKWebViewConfiguration();
_wkWebView = new WKWebView(Frame, config);
SetNativeControl(_wkWebView);
}
if(e.NewElement!=null)
{
var webview = Element as MyWebView;
var url = webview.Uri;
Control.LoadRequest(new NSUrlRequest(new NSUrl("https://drive.google.com/viewerng/viewer?embedded=true&url=" + webview.Uri)));
}
}
}
}
in xaml
Now you can use it in xaml like (you could open a new ContentPage that contains the WebView)
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<local:MyWebView Uri="https://www.pdfpdf.com/samples/Sample1.PDF"/>
</StackLayout>
Related
I use a base content page for all pages in my app. I want to add a gradient to the content page so I created LocalGradientContentPage and had BaseContentPage inherit LocalGradientContentPage. Each platform has a custom renderer and everything works perfectly on Android. The android custom renderer is called when BaseContentPage is used. The issue is with iOS. iOS never calls the custom renderer when using BaseContentPage. It only calls the custom renderer if I use LocalGradientContentPage directly. All my classes follow.
LocalGradientContentPage.cs
namespace MyNamespace.Forms
{
public class LocalGradientContentPage : ContentPage
{
public static readonly BindableProperty EndColorProperty = BindableProperty.Create(
nameof(EndColor),
typeof(Color),
typeof(LocalGradientContentPage),
Color.White);
public Color EndColor
{
get => (Color)GetValue(EndColorProperty);
set => SetValue(EndColorProperty, value);
}
public static readonly BindableProperty StartColorProperty = BindableProperty.Create(
nameof(StartColor),
typeof(Color),
typeof(LocalGradientContentPage),
Color.Black);
public Color StartColor
{
get => (Color)GetValue(StartColorProperty);
set => SetValue(StartColorProperty, value);
}
}
}
Android: LocalGradientContentPageRenderer.cs
[assembly: ExportRenderer(typeof(LocalGradientContentPage),typeof(LocalGradientContentPageRenderer))]
namespace MyNamespace.DroidRenderers
{
public class LocalGradientContentPageRenderer : PageRenderer
{
public LocalGradientContentPageRenderer(Context context) : base(context)
{
}
private Xamarin.Forms.Color StartColor { get; set; }
private Xamarin.Forms.Color EndColor { get; set; }
protected override void DispatchDraw(Canvas canvas)
{
var gradient = new LinearGradient(0, 0, 0, Height,
StartColor.ToAndroid(),
EndColor.ToAndroid(),
Shader.TileMode.Mirror);
var paint = new Paint()
{
Dither = true,
};
paint.SetShader(gradient);
canvas.DrawPaint(paint);
base.DispatchDraw(canvas);
}
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
if (e.OldElement != null || Element == null)
{
return;
}
if (!(e.NewElement is LocalGradientContentPage page)) return;
StartColor = page.StartColor;
EndColor = page.EndColor;
}
}
}
iOS:LocalGradientContentPage.cs
[assembly: ExportRenderer(typeof(LocalGradientContentPage), typeof(LocalGradientContentPageRenderer))]
namespace MyNamespace.iOSRenderers
{
public class LocalGradientContentPageRenderer : PageRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.OldElement != null) return;
if (e.NewElement is LocalGradientContentPage page)
{
var gradientLayer = new CAGradientLayer
{
Frame = View.Bounds,
Colors = new CGColor[] { page.StartColor.ToCGColor(), page.EndColor.ToCGColor() }
};
View.Layer.InsertSublayer(gradientLayer, 0);
}
}
}
}
BaseContentPage.cs
public partial class BaseContentPage : LocalGradientContentPage
{
public BaseContentPage() : base()
{
InitializeComponent();
}
}
Using like this works:
<views:LocalGradientContentPage
StartColor="Blue"
EndColor="HotPink">
</views:LocalGradientContentPage>
Using like this does not work and the iOS LocalGradientContentPageRenderer is never called:
<views:BaseContentPage
StartColor="Blue"
EndColor="HotPink">
</views:LocalGradientContentPage>
I've do not have linking but just in case I did instantiate LocalGradientContentPage in AppDelegate.cs. It does hit the constructor when instantiated this way.
_ = new LocalGradientContentPage();
Also, as stated earlier, this is working fine in Android and calls the custom renderer when BaseContentPage is used.
I'm really at a loss as to why this isn't working. Any help would be appreciated.
Well, I use you code and add the views:BaseContentPage to a tabbedPage, it works well.
I use it like:
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:App629"
mc:Ignorable="d"
x:Class="App629.TabbedPage1">
<!--Pages can be added as references or inline-->
<views:BaseContentPage
StartColor="Blue"
EndColor="HotPink"/>
<ContentPage Title="Tab 2" />
<ContentPage Title="Tab 3" />
<views:BaseContentPage StartColor="Red" EndColor="HotPink">
</views:BaseContentPage>
</TabbedPage>
Some suggestions:
I'm using the latest Xamarin.forms version.
Check if there are any other codes in the BaseContentPage that affect the custom renderer?
Try the code as me to check if it works?
Add a breakPoint there in the custom renderer to check if it hit the code.
I am using Device.OpenUri for opening pdf files in my application. This is working fine on android devices but on iPhone, this is not working. Following is my code:
Device.OpenUri(new Uri("mypdfurl"));
Following is the screenshot on iPhone when opening pdf.
For opening pdf on iPhone should I add any permissions? Are there any free NuGet packages for showing the pdf in the app?
You can directly open the pdf file on the WebView . In this way you need to use Custom Renderer on Android because Webview in Android doesn't support open remote pdf in default .
in code behind
using Xamarin.Forms;
namespace PdfLoader
{
public class PdfWebView:WebView
{
public static readonly BindableProperty UriProperty = BindableProperty.Create(propertyName: "Uri",
returnType: typeof(string),
declaringType: typeof(PdfWebView),
defaultValue: default(string));
public string Uri
{
get { return (string)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
}
}
in Android
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Xamarin.Forms.Platform.Android;
using Xamarin.Forms;
using PdfLoader.Droid;
using PdfLoader;
[assembly: ExportRenderer(typeof(PdfWebView), typeof(MyWebViewRenderer))]
namespace PdfLoader.Droid
{
public class MyWebViewRenderer : WebViewRenderer
{
public MyWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (Control != null)
{
var customWebView = Element as PdfWebView;
Control.Settings.AllowUniversalAccessFromFileURLs = true;
Control.Settings.JavaScriptEnabled = true;
Control.LoadUrl(string.Format("https://drive.google.com/viewerng/viewer?url={0}", customWebView.Uri.ToString()));
}
}
}
}
in xaml
<StackLayout VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<local:PdfWebView
Uri="{Binding xxx}"
Source="{Binding xxx}" //set them as same value
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
HeightRequest="300"
WidthRequest="300"
x:Name="pdf_Webview"/>
</StackLayout>
In MVC 5 you could assign a value to session in global.asx when the session started. Is there a way you can do this in .Net Core MVC? I have session configured but in the middleware it seems to get called on every request.
nercan's solution will work, but I think I found a solution that requires less code and may have other advantages.
First, wrap DistributedSessionStore like this:
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Session;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
public interface IStartSession
{
void StartSession(ISession session);
}
public class DistributedSessionStoreWithStart : ISessionStore
{
DistributedSessionStore innerStore;
IStartSession startSession;
public DistributedSessionStoreWithStart(IDistributedCache cache,
ILoggerFactory loggerFactory, IStartSession startSession)
{
innerStore = new DistributedSessionStore(cache, loggerFactory);
this.startSession = startSession;
}
public ISession Create(string sessionKey, TimeSpan idleTimeout,
TimeSpan ioTimeout, Func<bool> tryEstablishSession,
bool isNewSessionKey)
{
ISession session = innerStore.Create(sessionKey, idleTimeout, ioTimeout,
tryEstablishSession, isNewSessionKey);
if (isNewSessionKey)
{
startSession.StartSession(session);
}
return session;
}
}
Then register this new class in Startup.cs:
class InitSession : IStartSession
{
public void StartSession(ISession session)
{
session.SetString("Hello", "World");
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton<IStartSession, InitSession>();
services.AddSingleton<ISessionStore, DistributedSessionStoreWithStart>();
services.AddSession();
...
}
Full code is here:
https://github.com/SurferJeffAtGoogle/scratch/tree/master/StartSession/MVC
I use it in a live project. It works correctly. if you want to keep it when the application stops. You should use DistributedCache. For example, I'm using DistributedRedisCache.
Add to startup this code;
public void ConfigureServices(IServiceCollection services)
{
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(60);
options.Cookie.HttpOnly = true;
});
// for redis distributed cache
//services.AddDistributedRedisCache(options =>
// {
// options.InstanceName = $"{Configuration["DistributedRedisCacheInstance"]}";
// options.Configuration = $"{Configuration["DistributedRedisCacheHost"]}";
// });
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IHttpContextAccessor acc)
{
app.UseSession();
}
And add new session extension;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using System.Text;
namespace SampleApp
{
public static class SessionExtensions
{
public static void SetObjectAsJson<T>(this ISession session, string key, T value)
{
session.Set(key, Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value)));
}
public static T GetObjectFromJson<T>(this ISession session, string key)
{
session.TryGetValue(key, out byte[] dataByte);
string data = dataByte != null ? Encoding.UTF8.GetString(dataByte) : null;
return data == null ? default(T) : JsonConvert.DeserializeObject<T>(data);
}
}
}
And use get or set same this;
var sessionItem = httpContext.Session.GetObjectFromJson<string>("sessionItem");
//or
ContextProviderExtension.HttpContextAccessor.HttpContext.Session.SetObjectAsJson("sessionItem", sessionItem);
you need this extension;
using Microsoft.AspNetCore.Http;
using System;
namespace SampleApp
{
public static class ContextProviderExtension
{
static IHttpContextAccessor httpContextAccessor = null;
public static IHttpContextAccessor HttpContextAccessor
{
get { return httpContextAccessor; }
set
{
if (httpContextAccessor != null)
{
throw new Exception("");
}
httpContextAccessor = value;
}
}
}
}
I suppose it will work.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
namespace SampleApp
{
public class SessionMiddleware
{
private readonly RequestDelegate _next;
public SessionMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
var sessionItem = httpContext.Session.GetObjectFromJson<string>("test");
if (sessionItem == null)
httpContext.Session.SetObjectAsJson<string>("test", httpContext.Session.Id);//httpContext.Session.Id or set a value
await _next.Invoke(httpContext);
}
}
public static class SessionMiddlewareExtensions
{
public static IApplicationBuilder UseSessionMiddleware(this IApplicationBuilder app)
{
return app.UseMiddleware<SessionMiddleware>();
}
}
}
and add startup.cs Configure method after app.UseSession();
app.UseSessionMiddleware();
I am developing an App using Xamarin.Forms for listing the news from different sources. I use a webView to open the link corresponding to the news. But I want to show the progress while loading the webpage into web view, like the progress bar on Safari App. For this I have used the ProgressBar element like this:
<StackLayout>
<!-- WebView needs to be given height and width request within layouts to render. -->
<ProgressBar Progress ="" HorizontalOptions="FillAndExpand" x:Name="progress"/>
<WebView x:Name="webView"
HeightRequest="1000"
WidthRequest="1000"
VerticalOptions= "FillAndExpand"
Navigating="webOnNavigating"
Navigated="webOnEndNavigating"/>
</StackLayout>
and in the code I have used
void webOnNavigating (object sender, WebNavigatingEventArgs e)
{
progress.IsVisible = true;
}
void webOnEndNavigating (object sender, WebNavigatedEventArgs e)
{
progress.IsVisible = false;
}
But I want to show also the progress of loading the data, not just an indication that is loading and load. I want the user to know that the data are loading. Is there a way to achieve this.
The implementations should be platform specific via custom renders. Luckily this topics has been discussed already for different platforms here on SO.
The Android version based on this thread:
[assembly: ExportRenderer(typeof(WebView), typeof(GenericWebViewRenderer))]
namespace WebViewWithProgressBar.Droid
{
public class GenericWebViewRenderer : WebViewRenderer
{
Context ctx;
public GenericWebViewRenderer(Context context) : base(context)
{
ctx = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (Control == null)
return;
var progressBar = new Android.Widget.ProgressBar(ctx, null, Android.Resource.Attribute.ProgressBarStyleHorizontal);
Control.SetWebChromeClient(new MyWebChromeClient(progressBar));
Control.AddView(progressBar);
}
class MyWebChromeClient : Android.Webkit.WebChromeClient
{
Android.Widget.ProgressBar progressBar;
public MyWebChromeClient(Android.Widget.ProgressBar progressBar)
{
this.progressBar = progressBar;
}
public override void OnProgressChanged(Android.Webkit.WebView view, int newProgress)
{
progressBar.SetProgress(newProgress, true);
}
}
}
}
On iOS it is a bit trickier, here is a very simple mock that does it job pretty well:
[assembly: ExportRenderer(typeof(WebView), typeof(GenericWebViewRenderer))]
namespace WebViewWithProgressBar.iOS
{
public class GenericWebViewRenderer : ViewRenderer<WebView, UIWebView>
{
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
var progressBar = new UIProgressView(UIProgressViewStyle.Bar);
progressBar.TintColor = UIColor.Green;
progressBar.TrackTintColor = UIColor.Black;
progressBar.ProgressTintColor = UIColor.Red;
var webView = new UIWebView(Frame);
webView.AddSubview(progressBar);
SetNativeControl(webView);
Control.Delegate = new MyUIWebViewDelegate(progressBar);
webView.LoadRequest(new NSUrlRequest(new NSUrl("https://google.com")));
}
}
class MyUIWebViewDelegate : UIWebViewDelegate
{
UIProgressView progressBar { get; }
public MyUIWebViewDelegate(UIProgressView progressBar)
{
this.progressBar = progressBar;
}
public override void LoadStarted(UIWebView webView)
{
progressBar.SetProgress(0.1f, false);
}
public override void LoadingFinished(UIWebView webView)
{
progressBar.SetProgress(1.0f, true);
}
public override void LoadFailed(UIWebView webView, NSError error)
{
// TODO:
}
}
}
}
For more details please check here.
P.S.: This code examples are available on github.
I'm trying out MvvmCross with Xamarin 'classic'.
I've got it working with Android.
But I can't get it work for iOS. I've taken a look at the sample mentioned here (eh): MVVMCross support for Xamarin.iOS Storyboards
I'm really missing something.
What do i have:
A storyboard with only 3 controls on it. a label and 2 buttons. All 3
have names so i get the properties in the RootViewController class.
The basis setup.cs
AppDelegate.cs
[Register("AppDelegate")]
public partial class AppDelegate : MvxApplicationDelegate
{
UIWindow _window;
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
_window = new UIWindow(UIScreen.MainScreen.Bounds);
StoryBoardTouchViewPresenter sbPresenter = new StoryBoardTouchViewPresenter(this, _window, "MainStoryboard");
var setup = new Setup(this, _window);
setup.Initialize();
var startup = Mvx.Resolve<IMvxAppStart>();
startup.Start();
sbPresenter.MasterNavigationController.NavigationBar.Translucent = false;
sbPresenter.MasterNavigationController.SetNavigationBarHidden(false, false);
return true;
}
}
StoryBoardTouchViewPresenter (from MVVMCross: Is it possible to use Storyboard with ICommand navigation?) But the API is changed.
public class StoryBoardTouchViewPresenter : MvxTouchViewPresenter
{
public static UIStoryboard Storyboard = null;
public StoryBoardTouchViewPresenter(UIApplicationDelegate applicationDelegate, UIWindow window, string storyboardName, NSBundle StoryboardBundleOrNull = null)
: base(applicationDelegate, window)
{
Storyboard = UIStoryboard.FromName(storyboardName, StoryboardBundleOrNull);
}
public override void Show(IMvxTouchView view)
{
MvxViewController sbView = null;
try
{
sbView = (MvxViewController)Storyboard.InstantiateViewController(view.Request.ViewModelType.Name.Replace("Model", ""));
}
catch (Exception e)
{
Console.WriteLine("Failed to find storyboard view, did you forget to set the Storyboard ID to the ViewModel class name without the Model suffix ?" + e);
}
sbView.Request = view.Request;
base.Show(sbView);
}
}
The default App.cs in the Core project
public class App : Cirrious.MvvmCross.ViewModels.MvxApplication
{
public override void Initialize()
{
CreatableTypes()
.EndingWith("Service")
.AsInterfaces()
.RegisterAsLazySingleton();
RegisterAppStart<ViewModels.MainViewModel>();
}
}
The ViewModel:
public class MainViewModel : MvxViewModel
{
ITodoTaskService taskService;
IDataManager<TodoTask> tasks;
public MainViewModel(ITodoTaskService taskService)
{
this.taskService = taskService;
}
public async override void Start()
{
this.tasks = new DataManager<TodoTask>(await this.taskService.GetTodoTasksAsync());
this.tasks.MoveFirst();
Rebind();
base.Start();
}
private void Rebind()
{
this.Description = this.tasks.Current.Description;
NextCommand.RaiseCanExecuteChanged();
PreviousCommand.RaiseCanExecuteChanged();
}
private string description;
public string Description
{
get { return this.description; }
set
{
this.description = value;
RaisePropertyChanged(() => Description);
}
}
private MvxCommand nextCommand;
public MvxCommand NextCommand
{
get
{
this.nextCommand = this.nextCommand ?? new MvxCommand(NavigateToNext, CanNavigateNext);
return this.nextCommand;
}
}
private bool CanNavigateNext()
{
return this.tasks.CanMoveNext;
}
public void NavigateToNext()
{
this.tasks.MoveNext();
Rebind();
}
private MvxCommand previousCommand;
public MvxCommand PreviousCommand
{
get
{
this.previousCommand = this.previousCommand ?? new MvxCommand(NavigateToPrevious, CanNavigatePrevious);
return this.previousCommand;
}
}
private bool CanNavigatePrevious()
{
return this.tasks.CanMovePrevious;
}
public void NavigateToPrevious()
{
this.tasks.MovePrevious();
Rebind();
}
}
I tried all kind of things. At the moment i get an exception that the MainView cannot be found. Which i partly understand. in App.cs MainViewModel is the start up. But the controller is called RootViewController. I think the RootviewController should bind to my MainViewModel. But i don't know how.
How should I make MvvmCross with iOs working?
How should I name the parts?
MvvmCross' default view finder will look for a view called MainView. That view should be derived from MvxViewController or another IMvxTouchView type. If you don't want to name your view controller "MainView" then you need to create a custom view resolver.
My advice: just rename your RootViewController to MainView.