"Save Photos in Photo albums of iPhone or iPad using Xamarin Forms" However PCL is Deprecated - xamarin.forms

I am trying do to the thing that is the question in this Stack Overflow Post "https://stackoverflow.com/questions/49291495/save-photos-in-photo-albums-of-iphone-or-ipad-using-xamarin-forms". However in the answer for the post PCL is deprecated. What is a more updated answer? Thank you.

However in the answer for the post PCL is deprecated.
Actually , you just need to do the same thing in Share Projects . DependencyService is available in Xamarin.Forms , which you can refer https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/dependency-service/introduction .
in Forms
public interface ISavePhotosToAlbum
{
void SavePhotosWithStream(string name, Stream data);
}
in iOS
[assembly: Dependency(typeof(SaveToAlbum))]
namespace xxx.iOS
{
public class SaveToAlbum : ISavePhotosToAlbum
{
public void SavePhotosWithStream(string name,Stream stream)
{
var imageData = NSData.FromStream(stream);
var image = UIImage.LoadFromData(imageData);
image.SaveToPhotosAlbum((img, error) =>
{
});
}
}
}
And add the following code in info.plist
<key>NSCameraUsageDescription</key>
<string>App need to access your camera</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>App need to access your albums</string> // new in iOS 11 and later
<key>NSPhotoLibraryUsageDescription</key>
<string>App need to access your albums</string>
In addition , if you want to implement it in Android
in Android
[assembly: Dependency(typeof(SaveToAlbum))]
namespace xxx.Droid
{
public class SaveToAlbum : ISavePhotosToAlbum
{
public void SavePhotosWithStream(string name,Stream stream)
{
var documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
documentsPath = Path.Combine(documentsPath, "Orders", temp);
Directory.CreateDirectory(documentsPath);
string filePath = Path.Combine(documentsPath, name);
byte[] bArray = new byte[data.Length];
using (FileStream fs = new FileStream(filePath , FileMode.OpenOrCreate))
{
using (data)
{
data.Read(bArray, 0, (int)data.Length);
}
int length = bArray.Length;
fs.Write(bArray, 0, length);
}
}
}
}

Related

Xamarin.Forms Directory Picker

I would like to save a file in a user selected folder, thats why I would like to provide a directory list to user and user will be able to choose the directory where he wants to export the data. Unfortuntely I could not find any example for directory/folder picker, I just found a file picker which is not useful for me..
https://github.com/jfversluis/FilePicker-Plugin-for-Xamarin-and-Windows
Is there any component for picking a folder for Xamarin.Forms? Actually I am just doing for Android but we use Xamarin.forms
There is none I can think of.
With netstandard everything is way more simple as you can use the classic c# File api to get the folders.
You just have to know the mappings between special folders and android folders (per example):
System.Environment.SpecialFolder Path
ApplicationData INTERNAL_STORAGE/.config
Desktop INTERNAL_STORAGE/Desktop
LocalApplicationData INTERNAL_STORAGE/.local/share
MyDocuments INTERNAL_STORAGE
MyMusic INTERNAL_STORAGE/Music
MyPictures INTERNAL_STORAGE/Pictures
MyVideos INTERNAL_STORAGE/Videos
Personal INTERNAL_STORAGE
source: https://learn.microsoft.com/en-US/xamarin/android/platform/files/
same for ios:
https://learn.microsoft.com/en-US/xamarin/ios/app-fundamentals/file-system
But it's really easy to implement, just enumerate all folders and display them in a ListView.
EDIT: more details on implementation.
In fact you want to code a "directory explorer", it's easy, here is the concept.
You have a ListView in your Page
You have a Cancel button and a Select button in your Page
You have a CurrentPath in your ViewModel
You bind CurrentPath to the Title of your Page
You have an List<DirectoryViewModel> Directories in your ViewModel
Each time a user click on a item from the list:
You add the directory name in your current path
You get all the directories from the new path, and update your Directories property (don't forget RaisePropertyChange(nameof(Directories)))
The ListView will be updated accordingly
Each time you back:
You remove last part of your current path
same as before
If you arrive to root path "/", you do nothing when clicking on back.
Oh and you could use this Grid component to instead of the ListView, will be nicer ;)
https://github.com/roubachof/Sharpnado.Presentation.Forms#grid-Layout
You can edit this to make it work..
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Java.IO;
using Java.Util;
namespace Android.Basic.IO
{
public class DirectoryPicker : ListActivity
{
public const String START_DIR = "startDir";
public const String ONLY_DIRS = "onlyDirs";
public const String SHOW_HIDDEN = "showHidden";
public const String CHOSEN_DIRECTORY = "chosenDir";
public const int PICK_DIRECTORY = 43522;
private File dir;
private Boolean showHidden = false;
private bool onlyDirs = true;
public override void OnCreate(Bundle savedInstanceState, PersistableBundle persistentState)
{
base.OnCreate(savedInstanceState, persistentState);
Bundle extras = Intent.Extras;
dir = OS.Environment.ExternalStorageDirectory;
if (extras != null)
{
String preferredStartDir = extras.GetString(START_DIR);
showHidden = extras.GetBoolean(SHOW_HIDDEN, false);
onlyDirs = extras.GetBoolean(ONLY_DIRS, true);
if (preferredStartDir != null)
{
File startDir = new File(preferredStartDir);
if (startDir.IsDirectory)
{
dir = startDir;
}
}
}
SetContentView(Resource.Layout.folder_chooser_activity);
var title = dir.AbsolutePath.ToString();
Title = (title);
Button btnChoose = (Button)FindViewById(Resource.Id.btnChoose);
String name = dir.Name;
if (name.Length == 0)
name = "/";
btnChoose.Text = ("Choose " + "'" + name + "'");
btnChoose.Click += delegate
{
returnDir(dir.AbsolutePath);
};
ListView lv = this.ListView;
lv.TextFilterEnabled = (true);
if (!dir.CanRead())
{
Context context = ApplicationContext;
String msg = "Could not read folder contents.";
Toast.MakeText(context, msg, ToastLength.Long).Show();
return;
}
var files = filter(dir.ListFiles(), onlyDirs, showHidden);
String[] names = Names(files);
ListAdapter = (new ArrayAdapter<String>(this, Resource.Layout.folder_chooser_item, names));
lv.ItemClick += (ff, gg) =>
{
var position = gg.Position;
if (!files[gg.Position].IsDirectory)
return;
String path = files[position].AbsolutePath;
var intent = new Intent(this, typeof(DirectoryPicker));
intent.PutExtra(DirectoryPicker.START_DIR, path);
intent.PutExtra(DirectoryPicker.SHOW_HIDDEN, showHidden);
intent.PutExtra(DirectoryPicker.ONLY_DIRS, onlyDirs);
StartActivityForResult(intent, PICK_DIRECTORY);
};
}
protected void OnActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == PICK_DIRECTORY && resultCode == (int)Result.Ok)
{
Bundle extras = data.Extras;
String path = (String)extras.Get(DirectoryPicker.CHOSEN_DIRECTORY);
returnDir(path);
}
}
private void returnDir(String path)
{
Intent result = new Intent();
result.PutExtra(CHOSEN_DIRECTORY, path);
SetResult(Result.Ok, result);
Finish();
}
public List<File> filter(File[] file_list, bool onlyDirs, bool showHidden)
{
var files = new List<File>();
foreach (var file in file_list)
{
if (onlyDirs && !file.IsDirectory)
continue;
if (!showHidden && file.IsHidden)
continue;
files.Add(file);
}
Collections.Sort(files);
return files;
}
public String[] Names(List<File> files)
{
String[] names = new String[files.Count];
int i = 0;
foreach (var file in files)
{
names[i] = file.Name;
i++;
}
return names;
}
}
}
Start activity as result then catch in OnActivityResult
if (requestCode == DirectoryPicker.PICK_DIRECTORY && resultCode == Result.Ok)
{
Bundle extras = data.Extras;
String path = (String)extras.Get(DirectoryPicker.CHOSEN_DIRECTORY);
// do stuff with path
}

receive message from android beam in xamarin forms

I'm develop cross-platform mobile application that use NFC. I already check the xamarin android beam sample here. Now i'm trying implement the same sample using xamarin forms so i'm using dependency service to call the function from android project.
I already create Ndef message and send function:
using System;
using System.Text;
using Android.App;
using MyApp.Droid;
using Android.Nfc;
using Xamarin.Forms;
[assembly: Dependency(typeof(PhoneBeam))]
namespace MyApp.Droid
{
public class PhoneBeam : Activity, NfcAdapter.ICreateNdefMessageCallback, NfcAdapter.IOnNdefPushCompleteCallback, iBeam
{
private NfcAdapter nfcAdapter;
public void Beam()
{
nfcAdapter = NfcAdapter.GetDefaultAdapter(MainActivity.Instance);
nfcAdapter.SetNdefPushMessageCallback(this, MainActivity.Instance);
nfcAdapter.SetOnNdefPushCompleteCallback(this, MainActivity.Instance);
}
public NdefMessage CreateNdefMessage(NfcEvent evt)
{
DateTime time = DateTime.Now;
var text = ("Beam me up!\n\n" + "Beam : " +
time.ToString("HH:mm:ss"));
NdefMessage msg = new NdefMessage(
new NdefRecord[]{ CreateMimeRecord (
"application/com.companyname.MyApp",
Encoding.UTF8.GetBytes (text)) });
return msg;
}
public NdefRecord CreateMimeRecord(String mimeType, byte[] payload)
{
byte[] mimeBytes = Encoding.UTF8.GetBytes(mimeType);
NdefRecord mimeRecord = new NdefRecord(
NdefRecord.TnfMimeMedia, mimeBytes, new byte[0], payload);
return mimeRecord;
}
public void OnNdefPushComplete(NfcEvent e){}
}
}
However, I really don't know how to receive a message. In android beam sample, they implement it in mainactivity. Here's sample:
protected override void OnResume ()
{
base.OnResume ();
if (NfcAdapter.ActionNdefDiscovered == Intent.Action) {
ProcessIntent (Intent);
}
}
void ProcessIntent (Intent intent)
{
IParcelable [] rawMsgs = intent.GetParcelableArrayExtra (
NfcAdapter.ExtraNdefMessages);
NdefMessage msg = (NdefMessage) rawMsgs [0];
mInfoText.Text = Encoding.UTF8.GetString (msg.GetRecords () [0].GetPayload ());
}
So i want to implement in class file so i can use dependencyService. Is there a way to implement this?
Edit: I did the send function:
public NdefMessage CreateNdefMessage (NfcEvent evt)
{
DateTime time = DateTime.Now;
var text = ("Beam me up!\n\n" +
"Beam Time: " + time.ToString ("HH:mm:ss"));
NdefMessage msg = new NdefMessage (
new NdefRecord[] { CreateMimeRecord (
"application/com.companyname.MyApp", Encoding.UTF8.GetBytes (text))
});
return msg;
}
But it return as "NEW TAG COLLECTED: application/com.companyname.MyApp". I want to resume MyApp and show the message. But it didn't.

Xamarin.Forms and Plugin.Media: after about 20 photos something crashes

I have a problem with Xamarin.Forms ver. 2.3.4.224 and Plugin.Media ver. 2.6.2. The problem occurs after taking about 20 photos (depends from the device): basically the app crashes without any apparently reason.
If you want to replicate the error, I created a test project for you on GitHub. With my iPad Air or iPad Pro after about 30 photos (video iPad Air - iPad Pro). All devices are iOS ver. 10.3.1 and they have enough space to storage photos.
The app is very simple: you have two buttons one for taking a picture and the other one to pick a photo. If you take photos one after another, after about 20 (32 in an iPad Air) the app crashes. I'm just take photos with the Plugin.Media nothing more.
Any ideas are welcome.
Update
In my project I had a reference to Refractored.MvvmHelpers and I noticed if I remove it, I can take more pictures. I created my BaseViewModel with INotifyPropertyChanged and I noticed I can take more photos.
I created then a new project (you can find it on GitHub under cameratesteasy) without MVVM and there is just the code to take a photo like:
public partial class cameratesteasyPage : ContentPage
{
int count = 0;
public cameratesteasyPage()
{
InitializeComponent();
CrossMedia.Current.Initialize();
}
void UpdateCount()
{
count++;
CountLabel.Text = $"{count} times";
}
async void StartCameraTapped(object sender, System.EventArgs args)
{
using (var file = await CrossMedia.Current.TakePhotoAsync(
new StoreCameraMediaOptions {}))
{
if (file == null)
return;
UpdateCount();
}
}
async void StartCameraTakeTapped(object sender, System.EventArgs args)
{
var file = await CrossMedia.Current.PickPhotoAsync();
if (file == null)
return;
UpdateCount();
}
}
In this case the app shut down after 52 photos. I saved the log for Xcode and you can see it here.
I used Xamarin Profile and the memory level is always low. After about 30 photos, an error occurs in Xamarin Profiler
Finally I could create a Xamarin Profiler file
Also I noticed this kind of error occurs on iPads. The same app in an iPhone is working fine (apparently) or I didn't find up to now the number of photos before crashing.
Update /2
I decided to implement a native function for taking photo.
Interface
public interface ICamera
{
void TakePicture();
}
Implementation
using System;
using cameratest.iOS;
using Foundation;
using UIKit;
using Xamarin.Forms;
[assembly: Xamarin.Forms.Dependency(typeof(Camera_iOS))]
namespace cameratest.iOS
{
public class Camera_iOS : ICamera
{
static UIImagePickerController picker;
static Action<NSDictionary> _callback;
static void Init()
{
if (picker != null)
return;
picker = new UIImagePickerController();
picker.Delegate = new CameraDelegate();
}
class CameraDelegate : UIImagePickerControllerDelegate
{
public override void FinishedPickingMedia(
UIImagePickerController picker, NSDictionary info)
{
var cb = _callback;
_callback = null;
picker.DismissModalViewController(true);
cb(info);
}
}
public static void TakePicture(UIViewController parent,
Action<NSDictionary> callback)
{
Init();
picker.SourceType = UIImagePickerControllerSourceType.Camera;
_callback = callback;
parent.PresentModalViewController(picker, true);
}
public static void SelectPicture(UIViewController parent,
Action<NSDictionary> callback)
{
Init();
picker.SourceType = UIImagePickerControllerSourceType.PhotoLibrary;
_callback = callback;
parent.PresentModalViewController(picker, true);
}
public void TakePicture()
{
var rc = UIApplication.SharedApplication.KeyWindow.RootViewController;
TakePicture(rc, (obj) =>
{
var photo = obj.ValueForKey(
new NSString("UIImagePickerControllerOriginalImage")) as UIImage;
var documentsDirectory =
Environment.GetFolderPath(Environment.SpecialFolder.Personal);
// hardcoded filename, overwritten each time
string jpgFilename = System.IO.Path.Combine(documentsDirectory,
"Photo.jpg");
NSData imgData = photo.AsJPEG();
NSError err = null;
if (imgData.Save(jpgFilename, false, out err))
{
Console.WriteLine("saved as " + jpgFilename);
}
else
{
Console.WriteLine("NOT saved as " +
jpgFilename + " because" + err.LocalizedDescription);
}
});
}
}
}
With this code after about 30 photos, the app crashes. The only difference is with this code I can receive some alert from ReceiveMemoryWarning. If you have an interest, I updated the code on GitHub.

Making screen capture in xamarin.forms

Is there a package that does screen capture in xamarin.forms ?
I need also to capture google maps screen shots
Check out this blog post by Daniel Hindrikes.
I'm going to assume that you use a PCL for your shared code.
You will need to create an interface in your PCL. He calls it IScreenshotManager. The declaration looks like this:
public interface IScreenshotManager
{
Task<byte[]> CaptureAsync();
}
Now all platforms will have their own implementation for it.
For iOS;
public class ScreenshotManager : IScreenshotManager
{
public async System.Threading.Tasks.Task<byte[]> CaptureAsync()
{
var view = UIApplication.SharedApplication.KeyWindow.RootViewController.View;
UIGraphics.BeginImageContext(view.Frame.Size);
view.DrawViewHierarchy(view.Frame, true);
var image = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
using(var imageData = image.AsPNG())
{
var bytes = new byte[imageData.Length];
System.Runtime.InteropServices.Marshal.Copy(imageData.Bytes, bytes, 0, Convert.ToInt32(imageData.Length));
return bytes;
}
}
}
For Android:
public class ScreenshotManager : IScreenshotManager
{
public static Activity Activity { get; set; }
public async System.Threading.Tasks.Task<byte[]> CaptureAsync()
{
if(Activity == null)
{
throw new Exception("You have to set ScreenshotManager.Activity in your Android project");
}
var view = Activity.Window.DecorView;
view.DrawingCacheEnabled = true;
Bitmap bitmap = view.GetDrawingCache(true);
byte[] bitmapData;
using (var stream = new MemoryStream())
{
bitmap.Compress(Bitmap.CompressFormat.Png, 0, stream);
bitmapData = stream.ToArray();
}
return bitmapData;
}
}
And for Windows Phone:
public class ScreenshotManager : IScreenshotManager
{
public async Task<byte[]> CaptureAsync()
{
var rootFrame = Application.Current.RootVisual as PhoneApplicationFrame;
var screenImage = new WriteableBitmap((int)rootFrame.ActualWidth, (int)rootFrame.ActualHeight);
screenImage.Render(rootFrame, new MatrixTransform());
screenImage.Invalidate();
using (var stream = new MemoryStream())
{
screenImage.SaveJpeg(stream, screenImage.PixelWidth, screenImage.PixelHeight, 0, 100);
var bytes = stream.ToArray();
return bytes;
}
}
}
Don't forget to register your platform specific implementations with the attribute which registers it with the Dependency Service, like this:
[assembly: Xamarin.Forms.Dependency (typeof (ScreenshotManager))]
It goes above the namespace declaration.
Now from your shared code you would be able to get the byte[] of a screenshot with a call like this:
var screenshotBytes = DependencyService.Get<IScreenshotManager>().CaptureAsync();
You probably want to check if DependencyService.Get<IScreenshotManager>() isn't null before using it.
After that you can turn your byte[] into an image and do whatever you like with it!
Implementation for UWP
public async Task<byte[]> CaptureAsync()
{
//create and capture Window
var renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.RenderAsync(Window.Current.Content);
var pixelpuffer = await renderTargetBitmap.GetPixelsAsync();
var logicalDpi = DisplayInformation.GetForCurrentView().LogicalDpi;
IRandomAccessStream stream = new InMemoryRandomAccessStream();
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
encoder.BitmapTransform.InterpolationMode = BitmapInterpolationMode.Fant;
encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Straight, (uint)renderTargetBitmap.PixelWidth, (uint)renderTargetBitmap.PixelHeight, logicalDpi, logicalDpi, pixelpuffer.ToArray());
await encoder.FlushAsync();
byte[] resultingBuffer = new byte[stream.Size];
await stream.ReadAsync(resultingBuffer.AsBuffer(), (uint)resultingBuffer.Length, InputStreamOptions.None);
return resultingBuffer;
}

How can i use engine object in my console application

"How can i use engine in my console application"
I shouldn't use the ITemplate-interface and Transform-Method.
I am using Tridion 2011
Could anyone please suggest me.
You can't. The Engine class is part of the TOM.NET and that API is explicitly reserved for use in:
Template Building Blocks
Event Handlers
For all other cases (such as console applications) you should use the Core Service.
There are many good questions (and articles on other web sites) already:
https://stackoverflow.com/search?q=%5Btridion%5D+core+service
http://www.google.com/#q=tridion+core+service
If you get stuck along the way, show us the relevant code+configuration you have and what error message your get (or at what step you are stuck) and we'll try to help from there.
From a console application you should use the Core Service. I wrote a small example using the Core Service to search for items in the content manager.
Console.WriteLine("FullTextQuery:");
var fullTextQuery = Console.ReadLine();
if (String.IsNullOrWhiteSpace(fullTextQuery) || fullTextQuery.Equals(":q", StringComparison.OrdinalIgnoreCase))
{
break;
}
Console.WriteLine("SearchIn IdRef:");
var searchInIdRef = Console.ReadLine();
var queryData = new SearchQueryData
{
FullTextQuery = fullTextQuery,
SearchIn = new LinkToIdentifiableObjectData
{
IdRef = searchInIdRef
}
};
var results = coreServiceClient.GetSearchResults(queryData);
results.ToList().ForEach(result => Console.WriteLine("{0} ({1})", result.Title, result.Id));
Add a reference to Tridion.ContentManager.CoreService.Client to your Visual Studio Project.
Code of the Core Service Client Provider:
public interface ICoreServiceProvider
{
CoreServiceClient GetCoreServiceClient();
}
public class CoreServiceDefaultProvider : ICoreServiceProvider
{
private CoreServiceClient _client;
public CoreServiceClient GetCoreServiceClient()
{
return _client ?? (_client = new CoreServiceClient());
}
}
And the client itself:
public class CoreServiceClient : IDisposable
{
public SessionAwareCoreServiceClient ProxyClient;
private const string DefaultEndpointName = "netTcp_2011";
public CoreServiceClient(string endPointName)
{
if(string.IsNullOrWhiteSpace(endPointName))
{
throw new ArgumentNullException("endPointName", "EndPointName is not specified.");
}
ProxyClient = new SessionAwareCoreServiceClient(endPointName);
}
public CoreServiceClient() : this(DefaultEndpointName) { }
public string GetApiVersionNumber()
{
return ProxyClient.GetApiVersion();
}
public IdentifiableObjectData[] GetSearchResults(SearchQueryData filter)
{
return ProxyClient.GetSearchResults(filter);
}
public IdentifiableObjectData Read(string id)
{
return ProxyClient.Read(id, new ReadOptions());
}
public ApplicationData ReadApplicationData(string subjectId, string applicationId)
{
return ProxyClient.ReadApplicationData(subjectId, applicationId);
}
public void Dispose()
{
if (ProxyClient.State == CommunicationState.Faulted)
{
ProxyClient.Abort();
}
else
{
ProxyClient.Close();
}
}
}
When you want to perform CRUD actions through the core service you can implement the following methods in the client:
public IdentifiableObjectData CreateItem(IdentifiableObjectData data)
{
data = ProxyClient.Create(data, new ReadOptions());
return data;
}
public IdentifiableObjectData UpdateItem(IdentifiableObjectData data)
{
data = ProxyClient.Update(data, new ReadOptions());
return data;
}
public IdentifiableObjectData ReadItem(string id)
{
return ProxyClient.Read(id, new ReadOptions());
}
To construct a data object of e.g. a Component you can implement a Component Builder class that implements a create method that does this for you:
public ComponentData Create(string folderUri, string title, string content)
{
var data = new ComponentData()
{
Id = "tcm:0-0-0",
Title = title,
Content = content,
LocationInfo = new LocationInfo()
};
data.LocationInfo.OrganizationalItem = new LinkToOrganizationalItemData
{
IdRef = folderUri
};
using (CoreServiceClient client = provider.GetCoreServiceClient())
{
data = (ComponentData)client.CreateItem(data);
}
return data;
}
Hope this gets you started.

Resources