0

I am building an app that has several images(Plans) that are loaded and setup on a scrollview with a page content. I am trying to load the first image on the main thread and then load the surrounding images from it's index on a background thread. All the UIImageViews are in an array (planImages) to reference when I need to set their images with the loaded data.

The problem is the images aren't always updating with the recently loaded images. This is my first time using Dispatch, so is there something I am doing wrong? Here is the section of code that is giving me the issue.

    func setupPagesInScrollView(firstImage: Int)
{
    let numberOfPages: CGFloat = CGFloat(selectedDetails!.numberOfPlans)

    scrollView!.contentSize = CGSize(width: scrollView!.frame.width * numberOfPages, height: scrollView!.frame.height)
    pageControl!.numberOfPages = selectedDetails!.numberOfPlans

    if(planImages.count < selectedDetails!.numberOfPlans)
    {
        for index in planImages.count...selectedDetails!.numberOfPlans - 1
        {
            let iView = UIImageView()
            iView.frame = CGRect(x: 0, y: 0, width: scrollView!.frame.width, height: scrollView!.frame.height)
            planImages.append(iView)
            scrollView!.addSubview(iView)

            iView.translatesAutoresizingMaskIntoConstraints = false
            iView.topAnchor.constraint(equalTo: scrollView!.topAnchor).isActive = true
            iView.leftAnchor.constraint(equalTo: scrollView!.leftAnchor, constant: scrollView!.frame.width * CGFloat(index)).isActive = true
            iView.heightAnchor.constraint(equalToConstant: scrollView!.contentSize.height).isActive = true
            iView.widthAnchor.constraint(equalToConstant: scrollView!.frame.width).isActive = true
        }
    }

    DispatchQueue.global(qos: .background).async
        {
            //Load first selected image
            let path = self.selectedDetails!.dirPath + self.selectedDetails!.plansName
            self.setupImageAtIndex(path: path, index: firstImage)

            //Build Image data around first image on background threads
            var mask = 1
            let max = self.selectedDetails!.numberOfPlans

            while firstImage + mask < max || firstImage - mask > 0
            {
                if(firstImage + mask < max)
                {
                    self.setupImageAtIndex(path: path, index: firstImage + mask)
                }

                if(firstImage - mask >= 0)
                {
                    self.setupImageAtIndex(path: path, index: firstImage - mask)
                }

                mask = mask + 1
            }

            //Remove extra images from memory if needed
            if(self.planImages.count > self.selectedDetails!.numberOfPlans)
            {
                let dif = self.planImages.count - self.selectedDetails!.numberOfPlans

                for _ in 1...dif
                {
                    let index = self.planImages.count - 1
                    let plan = self.planImages[index]
                    self.planImages.remove(at: index)
                    plan.removeFromSuperview()
                    plan.image = nil
                }
            }
    }
}

private func setupImageAtIndex(path: String, index: Int)
{
    let imageName = index + 1 > 9 ? path + String(index + 1) : path + "0" + String(index + 1)

    planImages[index].image = UIImage(contentsOfFile: imageName)
}
Dane Caro
  • 75
  • 1
  • 7
  • You must update UI elements on the main queue, so at the very least your `addSubview` and `removeFromSuperview` calls need to be made in a `DispatchQueue.main.async` closure. Honestly you probably won't see much performance difference between just running everything on the main queue and dispatching work onto another queue, and you almost definitely don't want to use a `background` QoS. `userInitiaited` is probably a better choice. – Paulw11 Nov 20 '18 at 06:18
  • So I have made the changes and to my surprise this doesn't even preload the images into memory. The images don't appear to be loaded into memory until it is assigned to a view. Is there any way to force these images into memory so I don't get the load time delay when finally assigning it to the view? – Dane Caro Nov 20 '18 at 22:21
  • You should show your revised code. The typical approach with a scrollview is to maintain 3 "pages"; the page to the left, the visible page and the page to the right. When a left or right scroll completes you move the left/right/center images (and load a new image) and adjust the scrollview offset so that the visible page is back in the center. Don't create a "page" per image; that isn't efficient – Paulw11 Nov 20 '18 at 22:36

1 Answers1

0

As per apple documentation You must update UI elements on the main queue.

Update your code like this

private func setupImageAtIndex(path: String, index: Int)
    {
        let imageName = index + 1 > 9 ? path + String(index + 1) : path + "0" + String(index + 1)
        DispatchQueue.main.async {
            planImages[index].image = UIImage(contentsOfFile: imageName)

        }
    }
iOS_Maccus
  • 377
  • 1
  • 11