Search code examples
iosswiftuiscrollviewuicollectionviewfirebase-storage

UIScrollView or UICollectionView to swipe through images in order called from firebase?


My app is has a feature in which you answer questions about people you are following, such as (Option A, B, C, D are where the names for the users will be):

How the page should look

The idea is that you answer a question about a user and it randomly shuffles from the people you are following from the firebase database. The images are the profile pictures that a user uploads when signing up and they are shown in order from the top left, top right, bottom left, bottom right of whoever is in the answer of the question.

I am trying to use UIScrollView but all the images have stacked up on top of each other instead of being able to swipe between them (and the Question label has disappeared)

Is there a way to get the ScrollView to work or is there a better/more efficient way of integrating a UICollectionView (i've seen a few posts that recommend this for similar things)?

I'm new to this so would appreciate any help, thanks in advance :)


UPDATE:

I've edited the constraints so the left and right anchors of the image views should be correct and added a UIView on top of the UIScrollView on storyboard. I've also seen it could be done with UIPageViewController?

@IBOutlet weak var userImageScrollView: UIScrollView!
@IBOutlet weak var contentView: UIView!
@IBOutlet weak var questionLabel: UILabel!
@IBOutlet weak var optionA: UIButton!
@IBOutlet weak var optionB: UIButton!
@IBOutlet weak var optionC: UIButton!
@IBOutlet weak var optionD: UIButton!

func setupLayout() {
    userImageScrollView.isPagingEnabled = true
    userImageScrollView.isScrollEnabled = true

    userImageScrollView.addSubview(imageViewA)
    imageViewA.translatesAutoresizingMaskIntoConstraints = false
    imageViewA.widthAnchor.constraint(equalTo: userImageScrollView.widthAnchor).isActive = true
    imageViewA.heightAnchor.constraint(equalTo: userImageScrollView.heightAnchor).isActive = true
    imageViewA.topAnchor.constraint(equalTo: userImageScrollView.topAnchor).isActive = true
    imageViewA.bottomAnchor.constraint(equalTo: userImageScrollView.bottomAnchor).isActive = true
    imageViewA.leftAnchor.constraint(equalTo: userImageScrollView.leftAnchor)
    imageViewA.rightAnchor.constraint(equalTo: imageViewB.leftAnchor)
    imageViewA.contentMode = .scaleAspectFit

    userImageScrollView.addSubview(imageViewB)
    imageViewB.translatesAutoresizingMaskIntoConstraints = false
    imageViewB.widthAnchor.constraint(equalTo: userImageScrollView.widthAnchor).isActive = true
    imageViewB.heightAnchor.constraint(equalTo: userImageScrollView.heightAnchor).isActive = true
    imageViewB.bottomAnchor.constraint(equalTo: userImageScrollView.bottomAnchor).isActive = true
    imageViewB.leftAnchor.constraint(equalTo: imageViewA.rightAnchor)
    imageViewA.rightAnchor.constraint(equalTo: imageViewC.leftAnchor)
    imageViewB.contentMode = .scaleAspectFit

    userImageScrollView.addSubview(imageViewC)
    imageViewC.translatesAutoresizingMaskIntoConstraints = false
    imageViewC.widthAnchor.constraint(equalTo: userImageScrollView.widthAnchor).isActive = true
    imageViewC.heightAnchor.constraint(equalTo: userImageScrollView.heightAnchor).isActive = true
    imageViewC.bottomAnchor.constraint(equalTo: userImageScrollView.bottomAnchor).isActive = true
    imageViewC.leftAnchor.constraint(equalTo: imageViewB.rightAnchor)
    imageViewC.rightAnchor.constraint(equalTo: imageViewD.leftAnchor)
    imageViewC.contentMode = .scaleAspectFit

    userImageScrollView.addSubview(imageViewD)
    imageViewD.translatesAutoresizingMaskIntoConstraints = false
    imageViewD.widthAnchor.constraint(equalTo: userImageScrollView.widthAnchor).isActive = true
    imageViewD.heightAnchor.constraint(equalTo: userImageScrollView.heightAnchor).isActive = true
    imageViewD.bottomAnchor.constraint(equalTo: userImageScrollView.bottomAnchor).isActive = true
    imageViewD.leftAnchor.constraint(equalTo: imageViewC.rightAnchor)
    imageViewD.rightAnchor.constraint(equalTo: userImageScrollView.rightAnchor)
    imageViewD.contentMode = .scaleAspectFit

    self.userImageScrollView.delegate = self
}

Solution

  • The reason your imageViews are on top of each other is you define:

     imageViewB.leftAnchor.constraint(equalTo: imageViewA.leftAnchor)
    

    This translates to: the left of imageView B is the same as the left of imageViewA, essentially saying that their left sides both start at the same place, so they will be on top of each other.

    So you need to define each item from left to right:

    imageViewB.leftAnchor.constraint(equalTo: imageViewA.rightAnchor)
    

    imageViewBs left side, starts on the right of imageViewA. imageView Cs leftAnchor would be equal to imageViewBs right and so on, until you reach the final imageViewD, where you would need to have the leftAnchor equal to imageViewCs rightAnchor AND imageViewDs rightAnchor equal to the scrollViews rightAnchor to allow scrolling to work correctly.

    You could also do what you want easily with a scrollView if you want to scroll Down instead of left to right, instead of left and right anchors, use centerXAnchors to keep the views centered in the scrollView

    You need to imagine you are describing the layout to someone, ImageViewA is at the top, so its top anchor is equal to the scrollViews top anchor.

    With imageViewB you do the same describing it from the top down, so:

     imageViewB.topAnchor.constraint(equalTo: imageViewA.bottomAnchor).isActive = true
    

    The top of image view B starts at the bottom of imageView A, then you do the same with the rest like so:

     imageViewC.topAnchor.constraint(equalTo: imageViewC.bottomAnchor).isActive = true
    

    and

     imageViewD.topAnchor.constraint(equalTo: imageViewC.bottomAnchor).isActive = true
    

    Because its a scrollView, and you are scrolling down, the top (imageViewA) needs to be set to the top of the scroll view, and additionally the bottom item needs to define its bottomAnchor to the bottom of the scrollView in order for scrolling to work correctly:

      imageViewD.bottomAnchor.constraint(equalTo: userImageScrollView.bottomAnchor).isActive = true 
    

    You also are using centerXAnchor and leftAnchor together in some of the views, imagine the centerXAnchor is the same as leftAnchor but just describing it from a different part of the view, as your centerXAnchor comes after leftAnchor, it will just use the centerX, meaning that the image will be center in the scrollView.

    I'd recommend following this pattern and building your constraints again from scratch, really focusing on what anchors need to be defined and where. You could also use the Interface Builder to add the views, but learning to define constraints like this will prove to be a very valuable skill in future and helps to keep your code very clear and clean.

    UPDATE:

    This code will do exactly what you want, take note of the height and width of the scroll view and the constants I use to give space. Each imageView is defined the way I stated above, left to right, with the last item having left and right constraints. remove your setupLayout and replace it with this (making sure to change the constraints where necessary to suit your app):

     func setupLayout() {
        view.addSubview(scrollView)
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 50).isActive = true
        scrollView.heightAnchor.constraint(equalToConstant: 300).isActive = true
        scrollView.widthAnchor.constraint(equalToConstant: 300).isActive = true
        scrollView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        scrollView.backgroundColor = .gray
        scrollView.isPagingEnabled = true
    
        scrollView.addSubview(imageViewA)
        imageViewA.translatesAutoresizingMaskIntoConstraints = false
    
        imageViewA.widthAnchor.constraint(equalToConstant: 250).isActive = true
        imageViewA.heightAnchor.constraint(equalToConstant: 250).isActive = true
    
        imageViewA.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 25).isActive = true
        imageViewA.leftAnchor.constraint(equalTo: scrollView.leftAnchor, constant: 25).isActive = true
    
        imageViewA.backgroundColor = .cyan
    
        scrollView.addSubview(imageViewB)
        imageViewB.translatesAutoresizingMaskIntoConstraints = false
    
        imageViewB.heightAnchor.constraint(equalToConstant: 250).isActive = true
        imageViewB.widthAnchor.constraint(equalToConstant: 250).isActive = true
    
    
        imageViewB.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 25).isActive = true
        imageViewB.leftAnchor.constraint(equalTo: imageViewA.rightAnchor, constant: 25).isActive = true
    
        imageViewB.backgroundColor = .red
    
        scrollView.addSubview(imageViewC)
        imageViewC.translatesAutoresizingMaskIntoConstraints = false
    
        imageViewC.heightAnchor.constraint(equalToConstant: 250).isActive = true
        imageViewC.widthAnchor.constraint(equalToConstant: 250).isActive = true
    
    
        imageViewC.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 25).isActive = true
        imageViewC.leftAnchor.constraint(equalTo: imageViewB.rightAnchor, constant: 25).isActive = true
    
        imageViewC.backgroundColor = .black
    
        scrollView.addSubview(imageViewD)
        imageViewD.translatesAutoresizingMaskIntoConstraints = false
    
        imageViewD.heightAnchor.constraint(equalToConstant: 250).isActive = true
        imageViewD.widthAnchor.constraint(equalToConstant: 250).isActive = true
    
    
        imageViewD.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 25).isActive = true
        imageViewD.leftAnchor.constraint(equalTo: imageViewC.rightAnchor, constant: 25).isActive = true
        imageViewD.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: -25).isActive = true
    
        imageViewD.backgroundColor = .green
    
    
    }
    

    Here is a gif of what that code does:

    via GIPHY

    • Don't forget to call setupLayout in viewDidLoad()

       override func viewDidLoad() {
      super.viewDidLoad()
      
      setupLayout()
      // Do any additional setup after loading the view, typically from a nib.
       }