Search code examples
iosxamarin.iosxamarinxamarin-studio

How to display UIImage objects on a Collection View properly using Xamarin for iOS?


I'm working on some Xamarin examples of my own and to get a better understanding of making iOS apps and C# coding (I have more experience with Android programming).

I have a View where I'm displaying a Collection View and two Buttons, by pressing either I make a HTTP request to Imgur and retrieve different set of images. These images I want to display in the Collection View.

I make the requests with the following method in my Controller:

    public void makeRequest(string url){
        var request = (HttpWebRequest)WebRequest.Create(url);
        request.Headers.Add ("Authorization", "Client-ID " + clientId);
        request.Method = "GET";

        Task<WebResponse> task = Task.Factory.FromAsync (
                                     request.BeginGetResponse,
                                     asyncResult => request.EndGetResponse (asyncResult),
                                     (object)null);
        task.ContinueWith (t => ReadStreamFromResponse (t.Result));

    }

To parse the JSON response I use this method (I'm only interested in images, not albums):

    private void ReadStreamFromResponse(WebResponse response){
        using (Stream responseStream = response.GetResponseStream ()) {
            using (StreamReader sr = new StreamReader (responseStream)) {
                string content = sr.ReadToEnd ();
                var json = JsonObject.Parse (content);
                var array = json ["data"];
                foreach (JsonObject o in array) {
                      string url = o ["link"];
                  bool isAlbum = o ["is_album"];
                  if (!isAlbum) {
                    url = url.Insert (url.Length - 4, "s");
                            AddElement (url);
                  }
                }
            }
        }
    }
}

The method I use to try to add the Image to the Source is the following:

    public void AddElement(string url){
        using (var imgUrl = new NSUrl (url)) {
            using (var data = NSData.FromUrl (imgUrl)) {
                photoSource.photos.Add (UIImage.LoadFromData (data));
                InvokeOnMainThread (delegate {
                    PhotoCollection.ReloadData ();
                });
            }
        }

    }

This is my Source class:

class CollectionSource : UICollectionViewSource
{
    public List<UIImage> photos;
    public CollectionSource(){
        photos = new List<UIImage> ();
    }
    public CollectionSource(List<UIImage> photos){
        this.photos = photos;
    }

    public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath){
        var photoCell = (ImageCell)collectionView.DequeueReusableCell ((NSString) ImageCell.CELLID, indexPath);
        var photo = photos [indexPath.Row];

        photoCell.ImageView.Image = photo;

        return photoCell;
    }

    public override int GetItemsCount(UICollectionView collectionView, int section){
        return photos.Count;
    }

    public override int NumberOfSections(UICollectionView collectionView){
        return 1;
    }

    public override void ItemHighlighted(UICollectionView collectionView, NSIndexPath indexPath){
        var cell = collectionView.CellForItem (indexPath);
        cell.ContentView.BackgroundColor = UIColor.Yellow;
    }

    public override void ItemUnhighlighted(UICollectionView collectionView, NSIndexPath indexPath){
        var cell = collectionView.CellForItem (indexPath);
        cell.ContentView.BackgroundColor = UIColor.White;
    }

    public override bool ShouldHighlightItem(UICollectionView collectionView, NSIndexPath indexPath){
        return true;
    }

    public override bool ShouldShowMenu(UICollectionView collectionView, NSIndexPath indexPath){
        return true;
    }

    public override bool CanPerformAction(UICollectionView collectionView, MonoTouch.ObjCRuntime.Selector action, NSIndexPath indexPath, NSObject sender){
        return true;
    }

    public override void PerformAction(UICollectionView collectionView, MonoTouch.ObjCRuntime.Selector action, NSIndexPath indexPath, NSObject sender){
        Console.WriteLine ("Perform action");
    }


}

I avoided the use of CollectionView controller because I don't like to leave multiple operations to one class only, I like to split my code in classes.

This is my cell class:

public class ImageCell : UICollectionViewCell
{
    public const string CELLID = "ImageCell";

    [Export ("initWithFrame:")]
    public ImageCell (System.Drawing.RectangleF frame) : base(frame){
        this.Initialize ();
    }

    public UIImageView ImageView { 
        get;
        set;
    }
    private void Initialize(){
        //          this.ImageView = new UIImageView (this.ContentView.Bounds);
        this.ImageView = new UIImageView (UIImage.FromBundle("placeholder.png"));
        this.ContentView.AddSubview (this.ImageView);

    }
}

Finally this is my View Controller class:

partial class ViewController3 : UIViewController
{
    public CollectionSource photoSource;

    public ViewController3 (IntPtr handle) : base (handle)
    {
    }

    public override void ViewDidLoad ()
    {
        base.ViewDidLoad ();

        PhotoCollection.RegisterClassForCell (typeof(ImageCell), (NSString)ImageCell.CELLID);
        photoSource = new CollectionSource ();
        PhotoCollection.Source = photoSource;

        makeRequest (); 
    }
    // makeRequest, ReadStreamFromResponse and AddElement are located below this method, I won't place them again
}

As you can see I based my code on the Introduction to Collection Views article found on Xamarin guides. I was successful in downloading remote images now but they're not properly displayed on the Collection View.

The images are downloaded but as they are downloaded they flash across the Collection View and the result end look like this:

Collection View after images finished downloading

For some reason not all of the cell render the image correctly, perhaps am I wrongly refreshing my CollectionView?


Solution

  • So I found a solution to my problem but I have no idea why did it make it work.

    On my ImageCell class I added the following line of code to the Initialize method:

        private void Initialize(){
            this.ImageView = new UIImageView (); 
            this.ImageView.AutoresizingMask = ~UIViewAutoresizing.None;
            // if no image is assigned to the ImageView, then the cell won't be rendered correctly if later you add the image
            // not familiar with iOS guidelines so I don't know if that is the real reason
            this.ImageView = new UIImageView (UIImage.FromBundle("temp.png"));
    
            this.ContentView.AddSubview (this.ImageView);
    
        }
    

    With this the images are rendered correctly in the cells, still must improve the way I load this remote images but that's it, if you notice your cells aren't rendered correctly in place, then perhaps you should check if any component in your cell requires a placeholder to avoid leaving it empty.