I am trying to copy a library image to a persistent file path on an iOS device

Grime 791 Reputation points
2021-11-03T12:37:16.037+00:00

I can select an image from the device library and determine it's file path, but it appears that if I rebuild the app, in iOS, that path changes, so the image is effectively lost.
On Android, the path might be something like:

/DATA/USER/0/COM.MYWEB.APPNAME/FILES/2021013_145831.JPG

...and I can rebuild and deploy the app to my physical Android device and this path/filename is persistent.

On iOS, the path might be something like:

/var/mobile/Containers/Data/Application/A623D433-5D9A-43DA-B5D0-0EE477932BD4/Library/6B267A41-9865-43F2-A362-3EFA8EE5218A.jpeg-- 

The issue on iOS is that the path after /Application/ , and indeed after /Library/ , changes after every rebuild of the app so the path to the image is not persistent and is lost.

My hope is that I can rewrite the image to somewhere that will be persistent between builds.

This is my codebehind where I select a photo from the library and determine its path:

using HandsFreeNotes.Data;
using HandsFreeNotes.Model;
using HandsFreeNotes.ViewModel;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Essentials;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace HandsFreeNotes.View
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class OperatorInfoPage : ContentPage
    {

        OperatorModel _opm1;
        public OperatorModel opm1
        {
            get
            {
                return _opm1;
            }
            set
            {
                if (_opm1 != value)
                {
                    _opm1 = value;
                    OnPropertyChanged("opm1");
                }
            }
        }
        int SelectItemIndex;
        public EventHandler<OperatorModel> ReturnValue;

        public OperatorInfoPage(OperatorModel opm)
        {
            // add a bit of padding to cater to the "notch" on the iPhone.
            if (Device.RuntimePlatform == Device.iOS) { Padding = new Thickness(0, 40, 0, 0); }

            InitializeComponent();

            this.opm1 = opm;
            BindingContext = opm1;

            if (AvatarButton.Text == null)
            {
                AvatarButton.Text = "Click here to choose an avatar...";
            }
        }

        private async void BackButton_Clicked(object sender, EventArgs e)
        {
            await Navigation.PopModalAsync();
        }

        private async void OKButton_Clicked(object sender, EventArgs e)
        {

            var todoItem = (OperatorModel)BindingContext;
            PhotoPath = Preferences.Get("LastAvatar", null);

            if (PhotoPath != null)
            {
                todoItem.OperatorAvatar = PhotoPath;
                HFNDatabase database = await HFNDatabase.Instance;
                await database.SaveItemAsync(todoItem);
                Preferences.Set("LastAvatar", null);
            }

            EventHandler<OperatorModel> handler = ReturnValue;
            if (handler != null)
            {
                handler(this, todoItem);
            }
            await Navigation.PopModalAsync();
        }

        private async void CancelButton_Clicked(object sender, EventArgs e)
        {
            await Navigation.PopModalAsync();
        }

        private async void AvatarButton_Clicked(object sender, EventArgs e)
        {
            await TakePhotoAsync();
        }

        string PhotoPath;

        async Task TakePhotoAsync()
        {
            try
            {
                var photo = await MediaPicker.PickPhotoAsync();
                await LoadPhotoAsync(photo);
                var stream = photo.OpenReadAsync();
                // AvatarImage.Source = ImageSource.FromStream(() => stream);
                // await DisplayAlert("in", PhotoPath, "OK");
                AvatarButton.Text = PhotoPath;
                AvatarButton.TextColor = Color.DarkBlue;
                AvatarButton.FontSize = 14;
                AvatarImage.Source = PhotoPath;
                string NewOpName = NameEntry.Text;
                string NewOpAvatar = AvatarButton.Text;
                Preferences.Set("LastAvatar", PhotoPath);

            }

            catch (FeatureNotSupportedException fnsEx)
            {
                // Feature is not supported on the device
            }
            catch (PermissionException pEx)
            {
                // Permissions not granted
            }
            catch (Exception ex)
            {
                Console.WriteLine($"CapturePhotoAsync THREW: {ex.Message}");
            }
        }

        async Task LoadPhotoAsync(FileResult photo)
        {
            // canceled
            if (photo == null)
            {
                PhotoPath = null;
                return;
            }
            // save the file into local storage
            var newFile = Path.Combine(FileSystem.AppDataDirectory, photo.FileName);
            using (var stream = await photo.OpenReadAsync())
            using (var newStream = File.OpenWrite(newFile))
            await stream.CopyToAsync(newStream);
            PhotoPath = newFile;

            AvatarButton.Text = PhotoPath;
            AvatarButton.TextColor = Color.DarkBlue;
            AvatarButton.FontSize = 14;
            AvatarImage.Source = PhotoPath;
            string NewOpName = NameEntry.Text;
            string NewOpAvatar = AvatarButton.Text;
            Preferences.Set("LastAvatar", PhotoPath);
            // await DisplayAlert("Path to Avatar", PhotoPath, "Cancel");
            await Clipboard.SetTextAsync(PhotoPath);    // Xamarin Essentials clipboard grabber. GMB 1st time - 3/11/2021 
        }
    }
}
Developer technologies | .NET | Xamarin
0 comments No comments
{count} votes

Accepted answer
  1. Wenyan Zhang (Shanghai Wicresoft Co,.Ltd.) 36,436 Reputation points Microsoft External Staff
    2021-11-04T08:36:22.87+00:00

    Hello,

    Welcome to our Microsoft Q&A platform!

    I have to say you shouldn't store raw file paths if you want to avoid this issue. A better practice would be to only store the relative part of the path and always attach it to the current "root" path. You could have a try with the following code:

    Change the LoadPhotoAsync method by replacing PhotoPath .

      async Task LoadPhotoAsync(FileResult photo)  
      {  
          ..........  
          var newFile = Path.Combine(FileSystem.AppDataDirectory, photo.FileName);  
          using (var stream = await photo.OpenReadAsync())  
          using (var newStream = File.OpenWrite(newFile))  
              await stream.CopyToAsync(newStream);  
          PhotoPath = photo.FileName;  
          ............  
      }  
    

    When you display the image, you need to combine the path.

    AvatarImage.Source = Path.Combine(FileSystem.AppDataDirectory, PhotoPath);   
    

    I also add a CombineOperatorAvatar property to OperatorModel, and change the ItemsSource of OperatorListView and databing to test the effect , it works.

    Model

    public string CombineOperatorAvatar { get; set; }  
    

    SelectOperatorPage

     protected override async void OnAppearing()  
      {  
          base.OnAppearing();  
      
          HFNDatabase database = await HFNDatabase.Instance;  
          List< OperatorModel>dataList = await database.GetOperatorsAsync();  
          foreach (var OperatorModel in dataList)  
          {  
             OperatorModel.CombineOperatorAvatar = Path.Combine(FileSystem.AppDataDirectory, OperatorModel.OperatorAvatar);   
          }  
          OperatorListView.ItemsSource = dataList;  
      
      }  
    

    XAML

       <Image  
             
           Source="{Binding CombineOperatorAvatar}"  
           Grid.Row="0"  
           Grid.Column="0"  
           Margin="5"  
           HeightRequest="100"  
           WidthRequest="100"/>  
    

    Best Regards,
    Wenyan Zhang


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    1 person found this answer helpful.

0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.