I'm facing a somewhat weird issue.
I have a simple code (I guess) in my Xamarin Forms project to take pictures thanks to the great plugin.Media which allows cross platform picture capture:
public async Task<CameraResult> TakeCrossPictureAsync()
{
var file = await CrossMedia.Current.TakePhotoAsync(mediaOptions);
CameraResult res = new CameraResult()
{
Picture = ImageSource.FromFile(file.Path),
FilePath = file.Path,
Name = await this.UploadFileAsync(file.GetStream())
};
}
When I do this, I'm able to display the Image with this line in my ViewModel which is bound to an FFImageLoading:CachedImage
CameraResult camRes = await picManager.TakeCrossPictureAsync();
if (camRes != null)
this.Picture = camRes.Picture;
The "Picture" attribute is a Xamarin.Forms.ImageSource.
I then navigate to another page, passing the ImageSource as an argument and I'm able to display it in another FFImageLoading:CachedImage :
<ffimageloading:CachedImage Source="{Binding Picture}" LoadingPlaceholder="loading.png" CacheDuration="10" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"/>
No problem there, everything is working like a charm.
But, when I try to do the same thing by downloading the image from a Blob storage :
public async Task<ImageSource> GetFileAsync(string name)
{
var blob = this.container.GetBlobReference(name);
if (await blob.ExistsAsync())
{
await blob.FetchAttributesAsync();
byte[] blobBytes = new byte[blob.Properties.Length];
await blob.DownloadToByteArrayAsync(blobBytes, 0);
Stream stream = new MemoryStream(blobBytes);
return(ImageSource.FromStream(() => stream));
}
}
The Imagesource returned is bound to the same FFImageLoading:CachedImage in the first and second view.
The problem is that it displays perfectly in the first view but when I pass the ImageSource as an argument to the second ViewModel, the image does not show in the UWP project and displays the LoadingPlaceHolder or I get an unhandled exception on Android.
Is anyone able to tell me how the two ImageSources differ and why it is displayed from the file in the second view but not from the blob ?
Thank you in advance !
Related
Goal: Using an iOS native method, push a user made picture onto their Instagram feed in C#.
public bool ShareImage(byte[] imageByte)
{
bool result = false;
//string fileName = "xxx.png";
//string path = Path.Combine(FileSystem.CacheDirectory, fileName);
//File.WriteAllBytes(path, imageByte);
NSData data = NSData.FromArray(imageByte);
UIImage image = UIImage.LoadFromData(data);
//NSUrl url = NSUrl.FromString($"instagram://library?AssetPath={path}"); // Only opens
NSUrl url = NSUrl.FromString($"instagram://library?LocalIdentifier={1}");
if (UIApplication.SharedApplication.CanOpenUrl(url))
{
UIApplication.SharedApplication.OpenUrl(url);
}
return result;
}
As far as I'm aware the way I have to do this is to save my image to the device and get a Local Identifier for this.
I have constructed this from the snippets of objective-c code I have seen. Sharing photos only works with the native app installed, as I have learnt from my trials to get the facebook module working.
Edit: Using PHAssetChangeRequest from the iOS Photos namespace does not work.
A collegue has pointed out to me about the possibility of saving then using a photo picker to get the PHAsset for the Local Identifier. But this is an extra step I do not want the users of the App to go through. Better to just remove Instagram support as I just can go through the generic share method as shown below. The disadvantage of this method is that the user has then to pick the medium to share over.
public async void ShareImage(byte[] imageByte)
{
string fileName = "xxx.png";
string path = Path.Combine(FileSystem.CacheDirectory, fileName);
File.WriteAllBytes(path, imageByte);
await Share.RequestAsync(
new ShareFileRequest()
{
File = new ShareFile(path),
Title = "xxx"
}
);
}
Edit 2
Tried a different way using UIDocumentInteractionController but it is showing nothing and not posting, it is not throwing any exceptions to give me any clues as to what I'm doing wrong.
public bool ShareImage(byte[] imageByte)
{
bool result = false;
string fileName = "xxx.igo";
string path = Path.Combine(FileSystem.CacheDirectory, fileName);
File.WriteAllBytes(path, imageByte);
//string caption = "xxx";
string uti = "com.instagram.exclusivegram";
UIImageView view = new UIImageView();
//UIDocumentInteractionController controller = UIDocumentInteractionController.FromUrl(NSUrl.FromString(path));
UIDocumentInteractionController controller = new UIDocumentInteractionController
{
//controller.Url = NSUrl.FromString(path);
Url = NSUrl.FromFilename(path),
Uti = uti
};
//CoreGraphics.CGRect viewDimensions = new CoreGraphics.CGRect(0, 0, 200, 100);
_ = controller.PresentOpenInMenu(CoreGraphics.CGRect.Empty, view, true);
//_ = controller.PresentOpenInMenu(viewDimensions, view, true);
return result;
}
Edit 3
Using
UIView view = new UIImageView();
if (UIApplication.SharedApplication.KeyWindow.RootViewController is UIViewController uiController && uiController.View != null)
{
view = uiController.View;
}
I was able to get the possible share options to show. I was logged in to Instagram using the native App, but the post did not show.
Change
Url = NSUrl.FromFilename(path)
to
Url = new NSUrl(path,false)
The second way is pointing to the correct file path not the file name .
Change
controller.PresentOpenInMenu(CoreGraphics.CGRect.Empty, view, true);
to
controller.PresentOptionsMenu(UIScreen.MainScreen.Bounds, (UIApplication.SharedApplication.Delegate as AppDelegate).Window, true);
Show UIDocumentInteractionController inside current Window and set the proper frame.
I want to show an Image which I have successful stored into firebase storage. I got the user to pick an image from gallery and saved it on the database. I then saved the URL link in the real-time database, which I thought I could just bind to the image.source however this does not work. I have also tried copying the link from the firebase console straight into XAML that didn't work. I also tried downloading the image onto the phone(code below) how ever this doesn't work. I can't find any tutorials anywhere for displaying images from firebase for Xamarin forms. I'm a bit stuck here.
public void LoadImages(string imgurl)
{
var webClient = new WebClient();
byte[] imgBytes = webClient.DownloadData(imgurl);
//string img = Convert.ToBase64String(imgBytes);
petimage.Source = ImageSource.FromStream(() => new MemoryStream(imgBytes));
}
protected override void OnAppearing()
{
base.OnAppearing();
RetrivePetInfo();
}
private async void RetrivePetInfo()
{
var pet = await firebaseHelper.GetPet(PetName);
if (pet != null)
{
if(pet.imageUrl!=null)
{
LoadImages(pet.imageUrl);
}
}
}
Update your security rules with match /{allPaths=**} to indicate that public read and write access is allowed on all paths:
service firebase.storage {
match /b/zain*******.appspot.com/o {
match /{allPaths=**} {
// Allow access by all users
allow read, write;
}
}
}
I have uploaded image on my profile page and I want to hold that image until I logout in xamarin forms.
My image will be lost if I select another page so I want to hold it until I log out.
var profile = new Image { };
profile.Source = "profile.png";
profile.HorizontalOptions = LayoutOptions.StartAndExpand;
profile.VerticalOptions = LayoutOptions.StartAndExpand;
var profiletap = new TapGestureRecognizer();
profiletap.Tapped += async (s, e) =>
{
var file = await CrossMedia.Current.PickPhotoAsync();
if (file == null)
return;
await DisplayAlert("File Location", file.Path, "OK");
im = ImageSource.FromStream(() =>
{
var stream = file.GetStream();
//file.Dispose();
return stream;
});
profile.Source = im;
// await Navigation.PushModalAsync(new PhotoPage(im));
};
profile.GestureRecognizers.Add(profiletap);
Pages do not get destroyed when navigating to another page and coming back, which is why a page's constructor only gets executed the first time it is shown. So I am not sure what you mean when you say you want to hold that image.
Having said that, you could always assign the entire profile variable to a static global variable in your App class like below so that it stays the same no matter what. Then you would have to assign/initialize the global variable at the correct time.
But again, I am not sure if that is necessary, so you might try to explain more what the issue actually is:
In the App class:
public class App : Application {
public static Image ProfileImage = new Image {
Source = "profile.png",
HorizontalOptions = LayoutOptions.StartAndExpand,
VerticalOptions = LayoutOptions.StartAndExpand
};
....
}
Then in your page:
public class ProfilePage : ContentPage {
public ProfilePage() {
....
App.ProfileImage.GestureRecognizers.Add(profiletap);
}
}
Edit: See my answer here for an example of using a plugin to allow the user to choose a photo from their device's camera roll. Once you have the photo path, you can simply use HttpClient to send the image and a base64 string. There are plenty of other example online about how to do that.
Edit #2: After this line of your code:
var file = await CrossMedia.Current.PickPhotoAsync();
You now have the file and the path in file variable. So currently all you are doing is showing the image using ImageSource.FromStream but in order to keep showing the image when you return to the page, you need to also save the image to the device. In order to do that, you will need to write platform specific code in each project and reference that in your shared code. Something like this:
In your iOS and Android project, create a new file (FileHelper_Android.cs and FileHelper_iOS.cs for example) and add the following (the same code can be added to both iOS and Android files, just change the name of the class and file:
using ....;
[assembly: Dependency(typeof(FileHelper_Android))]
namespace YourNamespace.Droid{
/// <summary>
/// Responsible for working with files on an Android device.
/// </summary>
internal class FileHelper_Android : IFileHelper {
#region Constructor
public FileHelper_Android() { }
#endregion
public string CopyFile(string sourceFile, string destinationFilename, bool overwrite = true) {
if(!File.Exists(sourceFile)) { return string.Empty; }
string fullFileLocation = Path.Combine(Environment.GetFolderPath (Environment.SpecialFolder.Personal), destinationFilename);
File.Copy(sourceFile, fullFileLocation, overwrite);
return fullFileLocation;
}
}
}
Do the same on iOS and just change the file name. Now in your shared project you need to create IFileHelper.cs like so:
public interface IFileHelper {
string CopyFile(string sourceFile, string destinationFilename, bool overwrite = true);
}
Finally, in your page you would write the following:
_fileHelper = _fileHelper ?? DependencyService.Get<IFileHelper>();
profiletap.Tapped += async (s, e) =>
{
var file = await CrossMedia.Current.PickPhotoAsync();
if (file == null)
return;
await DisplayAlert("File Location", file.Path, "OK");
profile.Source = im;
imageName = "SomeUniqueFileName" + DateTime.Now.ToString("yyyy-MM-dd_hh-mm-ss-tt");
filePath = _fileHelper.CopyFile(file.Path, imageName);
im = ImageSource.FromFile(filePath)
// await Navigation.PushModalAsync(new PhotoPage(im));
};
Above, once the user chooses the file, we copy that file locally and the we also set your im variable to the new local file path, which gets returned from the IFileHelper.CopyFile method.
You still need to handle the case when the user comes back to the page or turns the app off and on again. In that situation, you need to load the saved image path. I would suggest either saving the image path into the DB, unless the user will only ever have a single profile image, then you could always just load that same path and filename. Let me know if you still have issues.
I try to get an image from my database (in this case localhost) but it doesn't seem to work. I'm binding the result (URL to the image) to my XAML without any luck. I get no error but an Image does not appear either. This is my code.
Maybe it has something to do with my URL?
When I used parse (as backend) they gave us a URL that started like this:
"http://files.parsetfss.com/....."
My URL just starts with "localhost". Do I need to adjust the backend code somehow?
My cs file where I retrieve the http and jsondata:
static public async Task<JObject> getImages ()
{
var httpClientRequest = new HttpClient ();
var result = await httpClientRequest.GetAsync ("http://www.localhost.com/image.php");
var resultString = await result.Content.ReadAsStringAsync ();
var jsonResult = JObject.Parse (resultString);
return jsonResult;
}
It looks like this on the localhost:
{
"results": [
{
"GetImage": {
"ID": 1,
"Name": "Aqua",
"URL": "http://localhost.com/Images/Aquavit.jpg"
}
}]
}
This is my contentpage where I try to recieve the image:
async void clickHere (object s, EventArgs ar)
{
var createImage = await phpApi.getImages ();
list = new List<pictures> ();
foreach (var currentItem in createImage["results"]) {
var prodPic = "";
if(currentItem["GetImage"] != null)
{
prodPic = (string)currentItem ["GetImage"]["URL"];
}
list.Add (new pictures () {
image = prodPic,
});
System.Diagnostics.Debug.WriteLine (currentItem ["GetImage"]["URL"]); //here I get the exact URL.
}
}
My class:
public class pictures
{
public string image { get; set; }
}
And its XAML:
<Image Source = "{Binding image}" />
Just in addition:
If you want to load more images and/or allow to display a placeholder while loading, I recomment the FFImageLoading library. It offers nice functionality like downloading, caching, showing placeholder and error images and most important: down sampling the image to the target size. This is always a pain on android.
You can bind strings that contain urls directly to the Source. The code would like like:
<ffimageloading:CachedImage
Source="http://thecatapi.com/?id=MTQ5MzcyNA">
</ffimageloading:CachedImage>
I think you should be using a UriImageSource instead of string. Since it's a url. Try this:
your class:
public class pictures
{
public UriImageSource image { get; set; }
}
contentpage:
prodPic = (string)currentItem ["GetImage"]["URL"];
list.Add (new pictures () {
image = new UriImageSource { Uri = new Uri(prodPic) },
});
Also I believe your list needs to be an ObservableCollection instead of a List.
I have table cell, in that I am showing multiple images in scrollview horizontally, I need load images into imageview asynchronously, that means I need to place default placefolder.png file, if the images are not loaded.
If the images are loaded I need to replace placeholder.png with the loaded images.
Can anyone please help me..
_image = new UIImageView(FromUrl(contentItem.FilePath, cachedImages)) { Tag = 1001 };
_imagePath = contentItem.FilePath;
_image.Frame = new RectangleF(x, 0, 250, _image.Image.CGImage.Height);
x = x + (Convert.ToInt32( Bounds.Width));
scrollwidth = scrollwidth + Convert.ToInt32( Bounds.Width);
_scrollView.ContentSize = new SizeF (scrollwidth, _image.Image.CGImage.Height);
_scrollView.Add (_image);
Since it is a table cell and you can't be certain if the cell is still the same when the task finishes I would create a download task similar to this first:
public async static Task<string> DownloadImage(string fileWebAddress, string localPath)
{
await new WebClient ().DownloadFileTaskAsync (url, fileName);
return localPath;
}
Then when creating the cell you would first check if you have already downloaded the image to local storage:
if (File.Exist(contentItem.FilePath))
{
SetImage(_image, contentItem.FilePath);
}
If not, then start async download and when it returns you check if the task was for the current cell (contentItem cannot be local function variable, it needs to be cell variable that can change):
else
{
DownloadImage(contentItem.WebPath, contentItem.FilePath).ContinueWith (t =>
{
if (contentItem.FilePath == t.Result)
{
SetImage(_image, t.Result);
}
});
}