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)")
}
}
}
}
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.
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.