Search code examples
iosswiftfirebaseuiscrollview

UIScrollView: Show user profile Images in a scroll view when user name is called from firebase


I'm building an app in which a user answers questions with a selection of 4 random users they are following. Currently it calls random names from my firebase database. Each user has a profile image they upload when signing up and this is saved in the same place as the users fullname.

I would like the profile image to be called at the same time as the fullnames and shown on the UIScrollView of the screen in the order that the names are called (e.g. so option A user image is shown first, then scroll to option B user Image etc) - like this image

So far, the fullnames are being called and shown on the button and I can access the firebase storage for the URL but I don't know how to get the profileImageUrl from firebase to show in this order on the scrollview when the users names are picked randomly, so far i have this:

@IBOutlet weak var pinionImagesScrollView: UIScrollView!
@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!

        let numberOfKeys = randomKeyArray.count
        var namesRemaining = numberOfKeys
        var names = [String]()
        var profileImages = [String]()

        for i in 0..<numberOfKeys {
            let thisUserKey = randomKeyArray[i]
            let userRef = self.ref.child("users").child(thisUserKey)
            userRef.observeSingleEvent(of: .value, with: { snapshot in
                let name = snapshot.childSnapshot(forPath: "fullname").value as! String
                let profileImageUrl = snapshot.childSnapshot(forPath: "profileImageUrl").value as! String
                print(name)
                namesRemaining -= 1
                names.append(name)

                // Another array for images
                print(profileImageUrl)
                profileImages.append(profileImageUrl)

                self.currIds.append(thisUserKey)
                if namesRemaining == 0 {
                    self.currNames = names
                    self.optionA.setTitle(names[0], for: .normal)
                    self.optionA.backgroundColor = UIColor.orange
                    self.optionB.setTitle(names[1], for: .normal)
                    self.optionB.backgroundColor = UIColor.orange
                    self.optionC.setTitle(names[2], for: .normal)
                    self.optionC.backgroundColor = UIColor.orange
                    self.optionD.setTitle(names[3], for: .normal)
                    self.optionD.backgroundColor = UIColor.orange
                }
            })
        }

Would appreciate any help/explanation, thanks :)

EDIT update of code:

@IBOutlet weak var imagePageControl: UIPageControl!
@IBOutlet weak var userImageScrollView: UIScrollView!
@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!

var user: UserModel?

override func viewDidLoad() {
    super.viewDidLoad()
    loadNewQuestion()
    getFourRandomNodesAndPrintUserName()
    imagePageControl.numberOfPages = namesWithUrl.count
    setupLayout()
}


var ref: DatabaseReference = Database.database().reference()
var currNames: [String] = []
var currIds: [String] = []
var names = [String]()
var namesWithUrl = [String : String]()
var frame = CGRect(x: 0, y: 0, width: 0, height: 0)
var imageViewA = UIImageView()
var imageViewB = UIImageView()
var imageViewC = UIImageView()
var imageViewD = UIImageView()

func setupLayout() {

    userImageScrollView.addSubview(imageViewA)
    imageViewA.translatesAutoresizingMaskIntoConstraints = false
    imageViewA.widthAnchor.constraint(equalTo: userImageScrollView.widthAnchor, constant: -10).isActive = true
    imageViewA.heightAnchor.constraint(equalToConstant: 60).isActive = true
    imageViewA.topAnchor.constraint(equalTo: userImageScrollView.topAnchor, constant: -5).isActive = true

    userImageScrollView.addSubview(imageViewB)
    imageViewB.translatesAutoresizingMaskIntoConstraints = false
    imageViewB.widthAnchor.constraint(equalTo: userImageScrollView.widthAnchor, constant: -10).isActive = true
    imageViewB.heightAnchor.constraint(equalToConstant: 60).isActive = true
    imageViewB.topAnchor.constraint(equalTo: imageViewA.bottomAnchor, constant: 5).isActive = true

    userImageScrollView.addSubview(imageViewC)
    imageViewC.translatesAutoresizingMaskIntoConstraints = false
    imageViewC.widthAnchor.constraint(equalTo: userImageScrollView.widthAnchor, constant: -10).isActive = true
    imageViewC.heightAnchor.constraint(equalToConstant: 60).isActive = true
    imageViewC.topAnchor.constraint(equalTo: imageViewB.bottomAnchor, constant: 5).isActive = true

    userImageScrollView.addSubview(imageViewD)
    imageViewD.translatesAutoresizingMaskIntoConstraints = false
    imageViewD.widthAnchor.constraint(equalTo: userImageScrollView.widthAnchor, constant: -10).isActive = true
    imageViewD.heightAnchor.constraint(equalToConstant: 60).isActive = true
    imageViewD.topAnchor.constraint(equalTo: imageViewC.bottomAnchor, constant: 5).isActive = true
    imageViewD.bottomAnchor.constraint(equalTo: userImageScrollView.bottomAnchor, constant: -5).isActive = true
}

   // Each view is attached to the bottom of the previous view, and the final must be attached to the bottom of the scroll view in order for it to scroll properly.

func getFourRandomNodesAndPrintUserName() {
    self.currNames = []
    self.currIds = []

    var myKeyArray = [String]()
    guard let uid = Auth.auth().currentUser?.uid else {
        return
    }

    let ref = self.ref.child("following").child(uid)
    //retreives all nodes in the following node
    ref.observeSingleEvent(of: .value, with: { snapshot in
        print(snapshot.children.allObjects)
        for child in snapshot.children { //build the array of keys
            let snap = child as! DataSnapshot
            let key = snap.key
            myKeyArray.append(key)
        }

        var randomKeyArray = [String]()
        let numFollowers = min(4, myKeyArray.count)
        for _ in 0..<numFollowers { //will iterate four times
            let count = myKeyArray.count //get the number of elements
            let randomInt = Int.random(in: 0..<count) //get a random index for the array
            let randomUserKey = myKeyArray[randomInt]
            randomKeyArray.append(randomUserKey)
            myKeyArray.remove(at: randomInt) //remove that object so it's not selected again
        }

        let numberOfKeys = randomKeyArray.count

        var namesRemaining = numberOfKeys

        for i in 0..<numberOfKeys {
            let thisUserKey = randomKeyArray[i]
            let userRef = self.ref.child("users").child(thisUserKey)
            userRef.observeSingleEvent(of: .value, with: { snapshot in
                let name = snapshot.childSnapshot(forPath: "fullname").value as! String
                let profileImageUrl = snapshot.childSnapshot(forPath: "profileImageUrl").value as! String

                print(name)
                print(profileImageUrl)
                namesRemaining -= 1
                self.names.append(name)
                self.namesWithUrl[name] = profileImageUrl


                self.currIds.append(thisUserKey)

                if numFollowers <= 3 {
                    self.optionA.setTitle("Follow\nat least\n4 friends!", for: .normal)
                    self.optionA.titleLabel?.textAlignment = .center
                    self.optionA.setTitleColor(UIColor.lightGray, for: .normal)
                    self.optionA.isEnabled = false
                    self.optionB.setTitle("Follow\nat least\n4 friends!", for: .normal)
                    self.optionB.titleLabel?.textAlignment = .center
                    self.optionB.setTitleColor(UIColor.lightGray, for: .normal)
                    self.optionB.isEnabled = false
                    self.optionC.setTitle("Follow\nat least\n4 friends!", for: .normal)
                    self.optionC.titleLabel?.textAlignment = .center
                    self.optionC.setTitleColor(UIColor.lightGray, for: .normal)
                    self.optionC.isEnabled = false
                    self.optionD.setTitle("Follow\nat least\n4 friends!", for: .normal)
                    self.optionD.titleLabel?.textAlignment = .center
                    self.optionD.setTitleColor(UIColor.lightGray, for: .normal)
                    self.optionD.isEnabled = false
                }
                else if namesRemaining == 0 {
                    self.currNames = self.names
                    self.optionA.setTitle(self.names[0], for: .normal)
                    self.optionA.backgroundColor = UIColor.orange
                    self.imageViewA.sd_setImageLoad(URL(namesWithUrl[name[0]])) //this is where i am getting the error - here you want to set the image to the imageView not the scrollView

                    self.optionB.setTitle(self.names[1], for: .normal)
                    self.optionB.backgroundColor = UIColor.orange

                    self.optionC.setTitle(self.names[2], for: .normal)
                    self.optionC.backgroundColor = UIColor.orange
                    self.optionD.setTitle(self.names[3], for: .normal)
                    self.optionD.backgroundColor = UIColor.orange
                }
                self.userImageScrollView.contentSize = CGSize(width: (self.userImageScrollView.frame.size.width * CGFloat(self.namesWithUrl.count)), height: self.userImageScrollView.frame.size.height)
                self.userImageScrollView.delegate = self
            })
        }
    })
}

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    let pageNumber = userImageScrollView.contentOffset.x / userImageScrollView.frame.size.width
    imagePageControl.currentPage = Int(pageNumber)
}

Update: how it looks

how images look now instead of being able to swipe between them

EDIT: Trying to make it so you can swipe between the images - but am getting error: Thread 1 SIGBART on app delegate

var frame = CGRect(x: 0, y: 0, width: 0, height: 0)

func setupLayout() {
    frame.origin.x = userImageScrollView.frame.size.width * CGFloat(4)
    frame.size = userImageScrollView.frame.size

    let imageViewA = UIImageView(frame: frame)
    userImageScrollView.addSubview(self.imageViewA)

    imageViewA.bottomAnchor.constraint(equalTo: userImageScrollView.bottomAnchor, constant: -5).isActive = true


    let imageViewB = UIImageView(frame: frame)
    userImageScrollView.addSubview(self.imageViewB)

    imageViewB.bottomAnchor.constraint(equalTo: userImageScrollView.bottomAnchor, constant: -5).isActive = true
    imageViewB.leftAnchor.constraint(equalTo: imageViewA.leftAnchor)

    let imageViewC = UIImageView(frame: frame)
    userImageScrollView.addSubview(imageViewC)

    imageViewC.bottomAnchor.constraint(equalTo: userImageScrollView.bottomAnchor, constant: -5).isActive = true
    imageViewC.leftAnchor.constraint(equalTo: imageViewB.leftAnchor)

    let imageViewD = UIImageView(frame: frame)
    userImageScrollView.addSubview(imageViewD)

    imageViewD.bottomAnchor.constraint(equalTo: userImageScrollView.bottomAnchor, constant: -5).isActive = true
    imageViewD.leftAnchor.constraint(equalTo: imageViewC.leftAnchor)

    self.userImageScrollView.contentSize = CGSize(width: (self.userImageScrollView.frame.size.width * CGFloat(4)), height: self.userImageScrollView.frame.size.height)
    self.userImageScrollView.delegate = self
}

Solution

  • Make a dictionary of names:profileImageUrl

    var namesWithUrl = [String : String]()
    

    and instead of just names.append(name) do:

    names.append(name)
    namesWithUrl[name] = profileImageUrl
    

    so now the name is held in a dictionary with its correct profileImageUrl and so the dictionary will look like:

    [name1value : name1URl, name2value : name2URl, ...]
    

    Then if you use SDWebImage you can do this very simply:

    self.optionA.setTitle(names, for: .normal)
    self.optionA.backgroundColor = UIColor.orange
    self.optionA.sd_setBackgroundImage(with: URL(namesWithUrl[name[1]]), for: .normal)
    //If using the button image
    
    self.optionB.setTitle(names[1], for: .normal)
    self.optionB.backgroundColor = UIColor.orange
    optionAImageView.sd_setImage(with: URL(namesWithUrl[name[1]]))
    //if using a seperate imageView
    

    That will easily set the image to your imageView, and you can just add that line under each one.

    EDIT: Adding image views to scroll view.

    You can add the image view through the storyboard, onto the scroll view or you can add them programmatically like so:

    First declare them:

    var imageViewA = UIImageView()
    var imageViewB = UIImageView()
    

    Then I like to have a setupLayout function to handle my views:

    func setupLayout() {
    
    pinionImagesScrollView.addsubview(imageViewA)
    imageViewA.translatesAutoResizingMaskIntoConstraints = false
    imageViewA.widthAnchor.constraint(equalTo: pinionImagesScrollView.widthAnchor, constant: -10).isActive = true
    imageViewA.heightAnchor.constraint(equalToConstant: 60).isActive = true
    imageViewA.topAnchor.constraint(equalTo: pinionImagesScrollView.topAnchor, constant: -5).isActive = true
    
    pinionImagesScrollView.addsubview(imageViewA)
    imageViewB.translatesAutoResizingMaskIntoConstraints = false
    imageViewB.widthAnchor.constraint(equalTo: pinionImagesScrollView.widthAnchor, constant: -10).isActive = true
    imageViewB.heightAnchor.constraint(equalToConstant: 60).isActive = true
    imageViewB.topAnchor.constraint(equalTo: imageViewA.bottomAnchor, constant: -5).isActive = true
    
    }
    

    There you can see how we are putting the imageViewB below imageViewA and the width is the same as the scrollview -10 giving a bit of space.

    then call setupLayout() in viewDidLoad(). they will be empty until you place the images on them. with optionAImageView.sd_setImage(with: URL(namesWithUrl[name[1]]))

    Update:

    You may need to change the String version of the url into a URL for SD_setimage like so:

     let optionAUrl = URL.init(string: nameUrls[names[0]])
      imageView.sd_setImage(with: optionAUrl)
    

    The subscript error may mean you are initializing the dictionary wrong as I have tested and it should work fine.

    Update:

    The problem with you setupLayout is you are mixing auto layout with using frames. I assume your scrollViews constraints are defined in storyboard so Here is how you would set up the imageViews, so that the width and height are the same as the scrollView, and you can scroll between them. Note if you would like to scroll horizontally, you should research about using a UICollectionView instead.

    In this example I am creating the scrollView programatically, you can just focus on the imageViews as your constraints are made in storyboard.

    import UIKit
    
    class ViewController: UIViewController {
    
    let scrollView = UIScrollView()
    let imageViewA = UIImageView()
    let imageViewB = UIImageView()
    let imageViewC = UIImageView()
    //Declaring the imageViews
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        setupLayout()
        // Do any additional setup after loading the view, typically from a nib.
    }
    
    func setupLayout() {
        view.addSubview(scrollView)
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        scrollView.bottomAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        scrollView.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -10).isActive = true
        scrollView.backgroundColor = .gray
        //setting up the size of the scrollView
    
        scrollView.addSubview(imageViewA)
        //This means we are using autoLayout, building the views and setting their size based on how their constraints relate to other objects
        imageViewA.translatesAutoresizingMaskIntoConstraints = false
        imageViewA.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
        //Setting the imageViewA top anchor to the scrollView topanchor we are essentially saying the top of this imageView starts at the top of the scroll view
        imageViewA.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: -10).isActive = true
        //Here the imageViews width is equal to the scrollViews width, (-10 is optional if you want it not to touch the edges)
        imageViewA.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true
         //CenterXAnchor here I am making sure the imageView is centered in the scrollView
        imageViewA.heightAnchor.constraint(equalTo: scrollView.heightAnchor, constant: -10).isActive = true
         //same with the height
        imageViewA.backgroundColor = .cyan
         //Background just for my testing as I dont have any images to sue
        imageViewA.contentMode = .scaleAspectFit
         //This is how you want the image to scale into the view for when you set it.
        scrollView.addSubview(imageViewB)
        imageViewB.translatesAutoresizingMaskIntoConstraints = false
        imageViewB.topAnchor.constraint(equalTo: imageViewA.bottomAnchor, constant: 10).isActive = true
        //Because we want to scroll down, the top anchor of imageViewB is equal to the bottom anchor of imageViewA meaning the top of B starts at the bottom of A
        imageViewB.heightAnchor.constraint(equalTo: scrollView.heightAnchor).isActive = true
       imageViewB.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: -10).isActive = true
        //Height and widths are the same.
        imageViewB.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true
        imageViewB.backgroundColor = .cyan
        imageViewB.contentMode = scaleAspectFit
        scrollView.addSubview(imageViewC)
        imageViewC.translatesAutoresizingMaskIntoConstraints = false
        imageViewC.topAnchor.constraint(equalTo: imageViewB.bottomAnchor, constant: 10).isActive = true
        //imageViewCs top starts at bottom of B
        imageViewC.heightAnchor.constraint(equalTo: scrollView.heightAnchor).isActive = true
        imageViewC.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: -10).isActive = true
        imageViewC.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true
        imageViewC.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
         //for the final view, it needs to be connected to the bottom so the scrollView is able to scroll.
        imageViewC.backgroundColor = .cyan
        imageViewC.contentMode = .scaleAspectFit
    }
    
    
    }
    

    This gives me a square Scrollview, which shows the imageViewA and allows me to scroll down and see imageViewB and then imageView C

    Output of this code, scrollView is set to centerYanchor of view so it stops at the middle, blue squares are imageViews, image1 shows how it starts you can see ImageViewA, then image2 shows how you can scroll to the next image: enter image description here

    enter image description here