Reputation: 171
I am working on adding buttons to UICollectionViewCell
at runtime and having the button registered to the event according to its type, at runtime. The type of the button can be, for instance, utterance button and action button, and both should be registered to separate events.
The hierarchy of UI elements is like UICollectionView
-> UICollectionViewCell
-> StackView
-> Multiple (UIView
-> UIButton
).
The problem is that I am unable to register the .touchUpInside
events.
What I have tried yet is calling becomeFirstResponder()
through button's instance, moving the event method inside the UIViewController
and adding target in cellForItemAt
method after having the view hierarchy set up, trying isUserInteractionEnabled
with values true
and false
on UICollectionView
, UICollectionViewCell
, UIStackView
, UIView
and UIButton
.
EDIT 1: So the code looks like the following:
class AssistantViewController: UIViewController {
private var assistantManager: AssistantManager
private var conversationSection: UICollectionView
private let layout:UICollectionViewFlowLayout = UICollectionViewFlowLayout.init()
private var multipleChoiceMessageCell = MultipleChoiceMessageCell()
private var conversationCells: [BaseCollectionViewCell] {
return [
multipleChoiceMessageCell
]
}
override func viewDidLoad() {
super.viewDidLoad()
construct()
assistantManager.performGreetingUtterance()
}
}
extension AssistantViewController: UICollectionViewDataSource {
@objc func didTapUtteranceButton() {
print ("elo I a m here")
}
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) ->
UICollectionViewCell {
let conversationItem = assistantManager.conversationItems[indexPath.row]
let cellId = self.getCellIdentifier(for: conversationItem)
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId,
for: indexPath) as? ConversationItemCell
if let conversationCell = cell {
conversationCell.layoutIfNeeded()
conversationCell.configure(with: conversationItem)
switch conversationItem {
case let .multipleChoiceMessage(chatMessage, multipleChoice): do {
for item in multipleChoice.multipleChoice.items {
switch item {
case .utteranceButton(let utteranceButton): do {
utteranceButton.button.isUserInteractionEnabled = true
utteranceButton.button.addTarget(self,
action: #selector(didTapUtteranceButton),
for: .touchUpInside)
//utteranceButton.button.becomeFirstResponder()
print("added")
}
}
}
}
default: print("Todo")
}
}
return cell!
}
class AssistantManager {
public func parseAssistantSnippets(
snippets: [Snippet]) -> ConversationCellModel {
var interactableItems: [MessageContent] = []
var utteredText:String = ""
for snippet in snippets {
switch snippet {
case .text(let text): utteredText = text.displayText
case .utteranceButton(let utteranceButton): do {
let button = UtteranceButton(buttonTitle: utteranceButton.displayText,
maxWidth: ConversationCell.elementMaxWidth,
utteranceText: utteranceButton.utteranceText,
onPress: performUserUtterance)
let messageContent = MessageContent.utteranceButton(button)
interactableItems.push(messageContent)
}
default: print("TODO")
}
}
return ConversationCellModel.init(message: utteredText,
multipleChoice: interactableItems)
}
public func performGreetingUtterance() {
self.addTypingIndicator()
performUtteranceOperation(utterance: "hi")
}
public func performUtteranceOperation(utterance: String) {
let context = UtteranceRequest.UserContext(cid: "1",
location: CLLocationCoordinate2D(),
storeID: "1",
timeZoneOffset: 0)
let request = UtteranceRequest(utterance: utterance,
context: context)
provider.conveyUtterance(request) { [weak self] result in
switch result {
case .success(let snippetResponse): do {
let snippetResponseTransformed = SnippetResponse.init(snippetResponse)
let ConversationCellModel = self?.parseAssistantSnippets(
snippets: snippetResponseTransformed.snippets)
if let model = ConversationCellModel {
self?.addMessageFromAssistant(interactableItems: model.multipleChoice,
utteredText: model.message)
}
}
case .failure(let error): return
}
}
}
}
class MultipleChoiceMessageCell: ConversationCell {
public func configure(with conversationItem: ConversationItem) {
switch conversationItem {
case let .multipleChoiceMessage(chatMessage, multipleChoice): do {
let isCellFromUser = chatMessage.metadata.isFromUser
let avatarBackgroundColor = chatMessage.metadata.avatarBackgroundColor
let avatarTintColor = chatMessage.metadata.avatarTintColor
let messageTextBackgroundColor = chatMessage.metadata.textContainerBackgroundColor
let messageTextColor = chatMessage.metadata.textColor
self.messageTextLabel.text = chatMessage.message
self.avatarImageView.image = chatMessage.metadata.avatarImage
self.messageTextContainer.backgroundColor = messageTextBackgroundColor
self.messageTextLabel.textColor = messageTextColor
self.avatarImageView.backgroundColor = avatarBackgroundColor
self.avatarImageView.tintColor = avatarTintColor
self.rightMessageHorizontalConstraint?.isActive = isCellFromUser
self.leftMessageHorizontalConstraint?.isActive = !isCellFromUser
self.rightAvatarHorizontalConstraint?.isActive = isCellFromUser
self.leftAvatarHorizontalConstraint?.isActive = !isCellFromUser
configureCellWith(interactablesSection: multipleChoice.multipleChoice.itemsContainer)
}
default:
logIncorrectMapping(expected: "message", actual: conversationItem)
}
}
func configureCellWith(interactablesSection: UIStackView) {
let stackViewTop = NSLayoutConstraint(
item: interactablesSection,
attribute: .top,
relatedBy: .equal,
toItem: self.messageTextContainer,
attribute: .bottom,
multiplier: 1,
constant: 10)
let stackViewLeading = NSLayoutConstraint(
item: interactablesSection,
attribute: .leading,
relatedBy: .equal,
toItem: self,
attribute: .leading,
multiplier: 1,
constant: ConversationCell.avatarMaxSize +
ConversationCell.messageContainerMaxLeadingMargin +
ConversationCell.avatarMaxMargin)
self.addAutoLayoutSubview(interactablesSection)
self.addConstraints([stackViewTop,
stackViewLeading])
}
}
class InteractableItem {
let contentView: UIView
var height: CGFloat
init() {
contentView = UIView()
height = 0
}
}
class CellPrimaryButton: InteractableItem {
let button: CorePrimaryButton
init(buttonTitle titleLabel: String, maxWidth: CGFloat) {
button = CorePrimaryButton()
button.setTitle(titleLabel, for: .normal)
button.setBackgroundColor(CoreColor.white, for: .normal)
button.setTitleColor(CoreColor.black, for: .normal)
button.layer.borderColor = CoreColor.black.cgColor
button.layer.borderWidth = 1
//button.becomeFirstResponder()
super.init()
contentView.isUserInteractionEnabled = true
// button.addTarget(self, action: #selector(didTapUtteranceButton), for: .touchUpInside)
setConstraints(maxWidth: maxWidth)
}
func setConstraints(maxWidth: CGFloat) {
let buttonHorizontal = NSLayoutConstraint(item: self.button,
attribute: .leading,
relatedBy: .equal,
toItem: self.contentView,
attribute: .leading,
multiplier: 1,
constant: 0)
let buttonWidth = NSLayoutConstraint(item: self.button,
attribute: .width,
relatedBy: .lessThanOrEqual,
toItem: nil,
attribute: .notAnAttribute,
multiplier: 1,
constant: maxWidth)
self.contentView.addConstraints([buttonHorizontal,
buttonWidth])
self.contentView.backgroundColor = UIColor.purple
self.height = self.button.intrinsicContentSize.height
self.contentView.addAutoLayoutSubview(self.button)
}
}
class UtteranceButton: CellPrimaryButton {
let utteranceText: String
let performUtterance: (String) -> Void
init(buttonTitle titleLabel: String,
maxWidth: CGFloat,
utteranceText: String,
onPress: @escaping (String) -> Void) {
self.utteranceText = utteranceText
self.performUtterance = onPress
super.init(buttonTitle: titleLabel, maxWidth: maxWidth)
}
}
Upvotes: 0
Views: 820
Reputation: 9915
Try this way of calling an action.
UIApplication.shared.sendAction(#selector(didTapUtteranceButton), to: self, from: nil, for: nil)
instead of this one:
utteranceButton.button.addTarget(self, #selector(didTapUtteranceButton), for: .touchUpInside)
Upvotes: 0