swiftuicollectionviewuikitstoryboard

Why can't the buttons be tapped in the simulator?


I am building an app that displays discount cards, like business cards for my local businesses.

Here is my CardData.swift:

import Foundation
import UIKit

struct CardData {
        let image: UIImage
        let facebookUrl: URL?
        let instagramUrl: URL?
        let websiteUrl: URL?
        let directionsUrl: URL?
}

class CustomCollectionViewCell: UICollectionViewCell {
    var imageView: UIImageView!
    var facebookButton: UIButton!
    var instagramButton: UIButton!
    var websiteButton: UIButton!
    var directionsButton: UIButton!
    var buttonStackView: UIStackView!
    
    var cardData: CardData? {
        didSet {
            updateUI()
        }
        
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupViews()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupViews() {
            // ImageView setup
            imageView = UIImageView(frame: bounds)
            imageView.contentMode = .scaleToFill
            imageView.clipsToBounds = true
            addSubview(imageView)

            // Create transparent buttons
            facebookButton = createTransparentButton()
            instagramButton = createTransparentButton()
            websiteButton = createTransparentButton()
            directionsButton = createTransparentButton()
            facebookButton.isUserInteractionEnabled = true
            instagramButton.isUserInteractionEnabled = true
            websiteButton.isUserInteractionEnabled = true
            directionsButton.isUserInteractionEnabled = true

            // Calculate button frame dimensions
            let buttonWidth: CGFloat = bounds.width / 4 // Divide width equally for 4 buttons
            let buttonHeight: CGFloat = 41.72  // Height of the buttons

            // Set button frames
            facebookButton.frame = CGRect(x: 0, y: bounds.height - buttonHeight, width: buttonWidth, height: buttonHeight)
            instagramButton.frame = CGRect(x: buttonWidth, y: bounds.height - buttonHeight, width: buttonWidth, height: buttonHeight)
            websiteButton.frame = CGRect(x: 2 * buttonWidth, y: bounds.height - buttonHeight, width: buttonWidth, height: buttonHeight)
            directionsButton.frame = CGRect(x: 3 * buttonWidth, y: bounds.height - buttonHeight, width: buttonWidth, height: buttonHeight)

            // Setting up the stack view for buttons
            buttonStackView = UIStackView(arrangedSubviews: [facebookButton, instagramButton, websiteButton, directionsButton])
            buttonStackView.distribution = .fillEqually
            buttonStackView.alignment = .fill
            buttonStackView.axis = .horizontal
            buttonStackView.frame = CGRect(x: 0, y: bounds.height - buttonHeight, width: bounds.width, height: buttonHeight)
            imageView.addSubview(buttonStackView)  // Add stack view to the imageView


            // Bring button stack view to the front
            bringSubviewToFront(buttonStackView)
        print("Facebook button frame: \(facebookButton.frame)")
        print("Instagram button frame: \(instagramButton.frame)")
        print("Website button frame: \(websiteButton.frame)")
        print("Directions button frame: \(directionsButton.frame)")


    }

    private func createTransparentButton() -> UIButton {
        let button = UIButton(type: .system)
        button.backgroundColor = .clear  // Transparent button
        button.tintColor = .clear  // No tint color
        return button
    }
    
    private func updateUI() {
        guard let data = cardData else { return }
        imageView.image = data.image
        
            // Manage button visibility and actions based on URL availability
        updateButtonVisibilityAndAction(facebookButton, url: data.facebookUrl)
        updateButtonVisibilityAndAction(instagramButton, url: data.instagramUrl)
        updateButtonVisibilityAndAction(websiteButton, url: data.websiteUrl)
        updateButtonVisibilityAndAction(directionsButton, url: data.directionsUrl)
        print("Facebook URL: \(data.facebookUrl?.absoluteString ?? "N/A")")
            print("Instagram URL: \(data.instagramUrl?.absoluteString ?? "N/A")")
            print("Website URL: \(data.websiteUrl?.absoluteString ?? "N/A")")
            print("Directions URL: \(data.directionsUrl?.absoluteString ?? "N/A")")
    }
    
    private func updateButtonVisibilityAndAction(_ button: UIButton, url: URL?) {
            button.isHidden = url == nil  // Hide if URL is nil
            
            // Log a message if the button is hidden
            if button.isHidden {
                    print("No Button Shown")
            } else {
                    print("Button Displayed")
            }
            
            if let actualURL = url {
                    button.addAction(UIAction { [weak self] action in
                            print("Button tapped") // Add this line to print a message when the button is tapped
                            self?.openURL(actualURL)
                    }, for: .touchUpInside)
            } else {
                    button.removeTarget(self, action: nil, for: .allEvents)  // Remove any existing actions
            }
    }
    
    private func openURL(_ url: URL) {
            // Attempt to open the URL directly if it's an HTTP or HTTPS URL
            if ["http", "https"].contains(url.scheme?.lowercased() ?? "") {
                    UIApplication.shared.open(url, options: [:], completionHandler: nil)
                    return
            }
            
            // Handle specific app URLs
            if let host = url.host?.lowercased() {
                    switch host {
                    case "facebook.com":
                            let appURL = URL(string: "fb://facewebmodal/f?href=\(url.absoluteString)")!
                            if UIApplication.shared.canOpenURL(appURL) {
                                    UIApplication.shared.open(appURL, options: [:], completionHandler: nil)
                                    return
                            }
                    case "instagram.com":
                            let appURL = URL(string: "instagram://app/\(url.absoluteString)")!
                            if UIApplication.shared.canOpenURL(appURL) {
                                    UIApplication.shared.open(appURL, options: [:], completionHandler: nil)
                                    return
                            }
                    case "maps.google.com":
                            let appURL = URL(string: "comgooglemaps://?daddr=\(url.query ?? "")")!
                            if UIApplication.shared.canOpenURL(appURL) {
                                    UIApplication.shared.open(appURL, options: [:], completionHandler: nil)
                                    return
                            }
                    default:
                            break
                    }
            }
            
            // Open the original URL if no specific handling was done
            UIApplication.shared.open(url, options: [:], completionHandler: nil)
    }

}

I had to remove the links in the ActivitesVC.swift because Stack Overflow was saying it was spam, but they did show up correctly in the logs.

Here is my ActivitiesVC.swift:

import SwiftUI
import Foundation
import UIKit

class Activities: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
    var collectionView: UICollectionView!
    var cards: [CardData] = [] // Array to hold card data
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupCollectionView()
        loadCards() // Function to load card data
    }
    
    private func setupCollectionView() {
        let layout = UICollectionViewFlowLayout()
        layout.itemSize = CGSize(width: view.frame.width, height: 144) // Adjust based on your cell size
        collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
        collectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: "CustomCell")
        collectionView.dataSource = self
        collectionView.delegate = self
        collectionView.backgroundColor = .white
        view.addSubview(collectionView)
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return cards.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as? CustomCollectionViewCell else {
            fatalError("Expected `CustomCollectionViewCell` type for reuseIdentifier 'CustomCell'.")
        }
        cell.cardData = cards[indexPath.row]
        return cell
    }
    
    private func loadCards() {
            // Define an array of dictionaries, where each dictionary represents the data for a single card
            let cardDataArray: [[String: Any]] = [
                    [
                            "imageName": "Blank Card Template",
                            "facebookUrl": "Specific url",
                            "instagramUrl": "Specific url",
                            "websiteUrl": "Specific url",
                            "directionsUrl": "Specific url"
                    ],
                    [
                            "imageName": "Blank Card Template",
                            "facebookUrl": "Specific url",
                            "instagramUrl": "Specific url",
                            "websiteUrl": "Specific url",
                            "directionsUrl": "Specific url"
                    ]
                    // Add more dictionaries as needed for additional cards
            ]

            // Iterate through the array of dictionaries and create CardData objects
            for cardDataDict in cardDataArray {
                    if let imageName = cardDataDict["imageName"] as? String,
                         let image = UIImage(named: imageName),
                         let facebookUrlString = cardDataDict["facebookUrl"] as? String,
                         let facebookUrl = URL(string: facebookUrlString),
                         let instagramUrlString = cardDataDict["instagramUrl"] as? String,
                         let instagramUrl = URL(string: instagramUrlString),
                         let websiteUrlString = cardDataDict["websiteUrl"] as? String,
                         let websiteUrl = URL(string: websiteUrlString),
                         let directionsUrlString = cardDataDict["directionsUrl"] as? String,
                         let directionsUrl = URL(string: directionsUrlString) {

                            // Create a CardData object and append it to the cards array
                            let card = CardData(image: image, facebookUrl: facebookUrl, instagramUrl: instagramUrl, websiteUrl: websiteUrl, directionsUrl: directionsUrl)
                            cards.append(card)
                    } else {
                            print("Failed to create CardData from dictionary: \(cardDataDict)")
                    }
            }
    }
}

Here is my storyboard navigator:

I have it set up in a UIViewController and it is supposed to load the data into the CollectionViewCells, which it does, each button has the correct URLs based on logs:

Facebook button frame: (0.0, 102.28, 98.25, 41.72)
Instagram button frame: (98.25, 102.28, 98.25, 41.72)
Website button frame: (196.5, 102.28, 98.25, 41.72)
Directions button frame: (294.75, 102.28, 98.25, 41.72)
Button Displayed
Button Displayed
Button Displayed
Button Displayed
Facebook URL: "Specific url"
Instagram URL: "Specific url"
Website URL: "Specific url"
Directions URL: "Specific url"
Facebook button frame: (0.0, 102.28, 98.25, 41.72)
Instagram button frame: (98.25, 102.28, 98.25, 41.72)
Website button frame: (196.5, 102.28, 98.25, 41.72)
Directions button frame: (294.75, 102.28, 98.25, 41.72)
Button Displayed
Button Displayed
Button Displayed
Button Displayed
Facebook URL: "Specific url"
Instagram URL: "Specific url"
Website URL: "Specific url"
Directions URL: "Specific url"

But I cannot tap the buttons inside the app.

Scene inside the simulator


Solution

  • The default value of imageView's isUserInteractionEnabled is false. So the subview button taps are ignored.

    Set imageView.isUserInteractionEnabled = true in setupViews(). Now button taps will work.