Saved image not showing on galary iOS(working fine on Android but not iOS)
Here is my code for saving image to device
public void SavePicture(string name, byte[] data, string location = "Downloads")
{
var documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
documentsPath = Path.Combine(documentsPath, location);
Directory.CreateDirectory(documentsPath);
string filePath = Path.Combine(documentsPath, name);
File.WriteAllBytes(filePath, data); // writes to local storage
}
Also allow for photo permission on info.plist
Privacy - Photo Library Usage Description
after execute this code I cant find the saved photo on iOS device
How to solve this?
Well in iOS to show an image into the gallery you have to write additional code as just using File.WriteAllBytes won't call the native UIImageWriteToSavedPhotosAlbum API to show it in the album.
To do that you will have to call this API as follows:
Make a dependency service:
public interface ISavePhotosToAlbum
{
void SavePhotosWithFilePath(String Path);
}
Now Add a native class and do the following:
[assembly: Dependency(typeof(SaveToAlbum))]
namespace SavePhotosDemo.iOS
{
public class SaveToAlbum : ISavePhotosToAlbum
{
public void SavePhotosWithFilePath(String Path)
{
using (var url = new NSUrl (Path))
using (var data = NSData.FromUrl (url))
var image = UIImage.LoadFromData (data);
image.SaveToPhotosAlbum((img, error) =>
{
if (error == null)
{//Success }
else
{//Failure}
});
}
}
}
Then use this Dependency Service as follows:
DependencyService.Get<ISavePhotosToAlbum>().SavePhotosWithStream(imageUrl);
Also, see to it that you add this dependency service call only for ios by adding it in an OnPlatform if statement.
UPDATE
Use the following code to Add a Custom Photo Album in Photos library:
void AddAssetToAlbum(UIImage image, PHAssetCollection album, string imageName)
{
try
{
PHPhotoLibrary.SharedPhotoLibrary.PerformChanges(() =>
{
// Create asset request
var creationRequest = PHAssetCreationRequest.CreationRequestForAsset();
var options = new PHAssetResourceCreationOptions
{
OriginalFilename = imageName
};
creationRequest.AddResource(PHAssetResourceType.Photo, image.AsJPEG(), options);
// Change asset request (change album by adding photo to it)
var addAssetRequest = PHAssetCollectionChangeRequest.ChangeRequest(album);
addAssetRequest.AddAssets(new PHObject[] { creationRequest.PlaceholderForCreatedAsset });
}, (success, error) =>
{
if (!success)
Console.WriteLine("Error adding asset to album");
});
}
catch (Exception ex)
{
string h = ex.Message;
}
}
See to it that you request authorization using the PHPhotoLibrary.RequestAuthorization method as we need to request access to be able to use the Photos library. Also, As of iOS 10 and above we also need to add an entry for access in the target .plist file for "Privacy - Photo Library Usage Description":
<key>NSPhotoLibraryUsageDescription</key>
<string>Access to photos is needed to provide app features</string>
Update
At the end, adding the following started showing the documents in the files app:
<key>UIFileSharingEnabled</key> <true/> <key>LSSupportsOpeningDocumentsInPlace</key> <true/>
Related
As follows, I edited info.plist and set it to associate ".atc" extension in Visual Studio 2019 for Mac.
Then, I was able to launch the application by double-clicking the "*.atc" file.
However, I don't know how to get the "full path" in the application by double-clicking the "*.atc" file. Is it written around AppDelegate.cs?
I solved it myself. I used the following example of a swift project as a reference.
ref. How to associate existing file type for Mac OS application in XCode 7?
First, add the "Document.cs" file, a class derived from NSDocument, to the project.
using System;
using AppKit;
using Foundation;
namespace AttacheCase
{
[Register("Document")]
public class Document : NSDocument
{
public Document(IntPtr handle) : base(handle)
{
// Add your subclass-specific initialization here.
}
public override void WindowControllerDidLoadNib(NSWindowController windowController)
{
base.WindowControllerDidLoadNib(windowController);
// Add any code here that needs to be executed once the windowController has loaded the document's window.
}
[Export("autosavesInPlace")]
public static bool AutosaveInPlace()
{
return true;
}
public override void MakeWindowControllers()
{
//Override to return the Storyboard file name of the document.
var storyboard = NSStoryboard.FromName("Main", null);
var wincontroller = (NSWindowController)storyboard.InstantiateControllerWithIdentifier("ViewController");
AddWindowController(wincontroller);
}
public override NSData GetAsData(string typeName, out NSError outError)
{
// Insert code here to write your document to data of the specified type.
// If outError != NULL, ensure that you create and set an appropriate error when returning nil.
throw new NotImplementedException();
}
public override bool ReadFromUrl(NSUrl url, string typeName, out NSError outError)
{
var alert = new NSAlert()
{
AlertStyle = NSAlertStyle.Critical,
InformativeText = "Information",
MessageText = url.Path,
};
alert.RunModal();
outError = null;
return true;
}
}
}
At this time, register the name of Window Controller by launching Xcode from Main.storyboard.
Now double click on the "*.atc" file, the application will launch and you will get the full path to the file.
I have a xamarin forms solution, in this I have 3 projects, the xamarin forms project, the android project and the iOS project. I have a file in the assets folder of my andoird project and I would like to can copy this file from the asset to the personal folder, so I can edit it.
I some places I have read that I can use AssetManager, but although I have using Xamarin.Forms in my view model of the xamarin forms project, I don't have access to Xamarin.Forms.Context, that it seems to be the way to can work with assets.
So how I could copy a file from the asset folder to the personal folder in my view model?
Thanks.
You could using DependencyService.
1.Create an interface
public interface ISaveFileService
{
void SaveFile();
}
2.Implement the interface on each platform (for example,for android)
in android project (may need request some permission):
public class SaveFileService : ISaveFileService
{
public SaveFile()
{
var filPath = System.IO.Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "YOUR_DATABASENAME");
if (!File.Exists(filPath))
{
try
{
using (var fileAssetStream = Android.App.Application.Context.Assets.Open("your file"))
using (var fileStream = new FileStream(filPath, FileMode.OpenOrCreate))
{
var buffer = new byte[1024];
int b = buffer.Length;
int length;
while ((length = await fileAssetStream.ReadAsync(buffer, 0, b)) > 0)
{
await fileStream.WriteAsync(buffer, 0, length);
}
fileStream.Flush();
fileStream.Close();
fileAssetStream.Close();
}
}
catch (Exception ex)
{
//Handle exceptions
}
}
}
}
3.Call it in your forms project like:
DependencyService.Get<ISaveFileService>().SaveFile();
I've looked at and tested extensively as this post. Below is the code for a screenshot button on PCL.
async void OnScreenshotButtonClicked(object sender, EventArgs args) {
var imageSender = (Image)sender;
var ScreenShot = DependencyService.Get<IScreenshotManager>().CaptureAsync();
await DisplayAlert("Image Saved", "The image has been saved to your device's picture album.", "OK");
}
So my question is how to turn byte[] into an image and save it to a device... For the life of me, I cannot figure out a way to save the image to the device. Any help would be greatly appreciated!!! Thanks!
Like Jason mentioned in the comments you can extend your code in the platform project by adding a File.WriteAllBytes(); line and save your file from right there.
Or you can decouple it and create another DependencyService. For instance create the IScreenshotService like: DependencyService.Get<IScreenshotService>().SaveScreenshot("MyScreenshot123", ScreenShot);
And create implementations like this:
PCL
public interface IScreenshotService
{
SaveScreenshot(string filename, byte[] imageBytes);`
}
iOS
[assembly: Xamarin.Forms.Dependency(typeof(ScreenshotService_iOS))]
namespace YourApp.iOS
{
public class ScreenshotService_iOS: IScreenshotService
{
public void SaveScreenshot(string filename, byte[] imageBytes)
{
var screenshot = new UIImage(NSData.FromArray(imageBytes));
screenshot.SaveToPhotosAlbum((image, error) =>
{
// Catch any error here
if(error != null)
Console.WriteLine(error.ToString());
// If you want you can access the image here from the image parameter
});
}
}
}
Android
[assembly: Xamarin.Forms.Dependency(typeof(Picture_Droid))]
namespace YourApp.Droid
{
public class ScreenshotService_Android : IScreenshotService
{
public void SaveScreenshot(string filename, byte[] imageBytes)
{
var dir = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDcim);
var pictures = dir.AbsolutePath;
// You might want to add a unique id like GUID or timestamp to the filename, else you could be overwriting an older image
string filePath = System.IO.Path.Combine(pictures, filename);
try
{
// Write the image
File.WriteAllBytes(filePath, imageData);
// With this we add the image to the image library on the device
var mediaScanIntent = new Intent(Intent.ActionMediaScannerScanFile);
mediaScanIntent.SetData(Uri.FromFile(new File(filePath)));
Xamarin.Forms.Forms.Context.SendBroadcast(mediaScanIntent);
}
catch(Exception e)
{
Console.WriteLine(e.ToString());
}
}
}
}
Don't forget to add the appropriate permissions on the Android app (probably WRITE_EXTERNAL_STORAGE is enough)
Lastly you could look at third-party libraries like Splat for example.
I am working on a chat app:
If the user taps on an image, it will show in full size. The following method is called:
Handle_Tapped():
void Handle_Tapped(object sender, System.EventArgs e)
{
try
{
Image image = sender as Image;
string filePath = String.Empty;
filePath = image.Source as FileImageSource;
Eva3Toolkit.CommonUtils.openImage(filePath);
}
catch (Exception ex)
{
throw ex;
}
}
Which calls (depending on the OS):
openImage() in Android:
public void openImage(string filePath)
{
Intent sendIntent = new Intent(Intent.ActionView);
sendIntent.SetDataAndType(Android.Net.Uri.Parse("file:" + filePath), "image/*");
Forms.Context.StartActivity(Intent.CreateChooser(sendIntent, "Bild öffnen..."));
}
openImage() in iOS:
public void openImage(string filePath)
{
var firstController = ((UIApplicationDelegate)(UIApplication.SharedApplication.Delegate)).Window.RootViewController.ChildViewControllers[0].ChildViewControllers[1].ChildViewControllers[0];
var navcontroller = firstController as UINavigationController;
var docIC = UIDocumentInteractionController.FromUrl(new NSUrl(filePath, true));
docIC.Delegate = new DocInteractionC(navcontroller);
docIC.PresentPreview(true);
}
Now I want to create a method openImage() in UWP, but I don't know know. I know that I will most likely have to work with the image as a StorageFile instead of the path because only a StorageFile grants me permission to open the image.
Is there a way to open the image in full size in UWP? I highly prefer to not create a new view for this.
A Launcher can open a file with the program that is assigned to the file:
public async void openImageAsync(string filePath)
{
StorageFile storageFile = await StorageFile.GetFileFromPathAsync(filePath);
await Launcher.LaunchFileAsync(storageFile);
}
For my situation it's working that I use filePath because the files are in a local folder.
The output looks like this:
I'm using the IMediaPicker lines to open gallery and pick an image:
using XLabs.Platform.Services.Media;
using XLabs.Platform.Device;
using XLabs.Ioc;
using Xamarin.Forms;
private async Task<string> pickImage(){
var device = Resolver.Resolve<IDevice>();
IMediaPicker mediaPicker = DependencyService.Get<IMediaPicker>() ?? device.MediaPicker;
if (mediaPicker == null)
throw new NullReferenceException("Media picker initialize error");
string ImageSource = null;
try
{
if (mediaPicker.IsPhotosSupported)
{
var mediaFile = await mediaPicker.SelectPhotoAsync(new CameraMediaStorageOptions
{
MaxPixelDimension = 400
});
ImageSource = mediaFile.Path;
}
}
catch (System.Exception)
{
}
return ImageSource;
}
However when I trigger the function in iPad it look like this:
The gallery only shows up as the size of an iPhone4 and there seems to be no where to change the frame or size. All I want is to display a full screen for it.
It worked well in android tablets.
Is there a work around?