I have a "View Controller" and a "Main View", now I have a "child view" that creates a custom view that returns a switch and a label related to that, and I add several of these custom views to my Main View. My question : Is there anyway possible to set button target and access these child views with only setting the delegate in parent View? or each of them needs it own delegate?
child View:
import UIKit
class RowView: UIView {
var title: String
var isOn: Bool
init(title: String, isOn: Bool) {
self.title = title
self.isOn = isOn
super.init(frame: .zero)
setupViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let myLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 14)
label.textColor = .black
label.textAlignment = .center
return label
}()
public let mySwitch:UISwitch = {
let mySwitch = UISwitch()
mySwitch.layer.borderColor = UIColor.white.cgColor
mySwitch.layer.borderWidth = 0.8
mySwitch.layer.cornerRadius = 14
// mySwitch.tag = num
return mySwitch
}()
func setupViews() {
myLabel.text = title
mySwitch.isOn = isOn
addSubview(myLabel)
addSubview(mySwitch)
myLabel.anchor(top: topAnchor, leading: leadingAnchor, bottom: nil, trailing: nil, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
mySwitch.anchor(top: nil, leading: nil, bottom: nil, trailing: trailingAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
mySwitch.centerYAnchor.constraint(equalTo: myLabel.centerYAnchor).isActive = true
}
// this is the suggested size for this view
override var intrinsicContentSize: CGSize {
return CGSize(width: 350, height: 31)
}
}
Parent View:
import UIKit
protocol settingDelegate: AnyObject {
func removeAllFavorites()
//func activateNotification(isOn: Bool)
//func allowNotificationAlert(isOn: Bool)
}
class SettingView: UIView {
//MARK: - Properties
var delegate: settingDelegate?
var notifView: UIView?
var alertView: UIView?
let deleteButton: UIButton = {
let button = UIButton(type: .system)
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 18)
button.setTitle("Delete Favorite Quotes", for: .normal)
button.backgroundColor = UIColor.red
button.setTitleColor(.white, for: .normal)
button.layer.cornerRadius = 5
button.titleLabel?.adjustsFontSizeToFitWidth = true // adjust button text to the size
button.titleLabel?.minimumScaleFactor = 0.5 // make it 50% smaller at max
button.contentEdgeInsets = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
button.addTarget(self, action: #selector(deleteHandler), for: .touchUpInside)
button.setContentHuggingPriority(UILayoutPriority(1000), for: .horizontal)
return button
}()
//MARK: - Initializers
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK: - Helper Methods
func setupViews() {
backgroundColor = .white
notifView = RowView(title: "Show day's quote as a notification", isOn: false)
alertView = RowView(title: "Ring an alert when notification is played", isOn: false)
guard let notifView = notifView, let alertView = alertView else { return }
let switchStack = UIStackView(arrangedSubviews: [notifView, alertView])
switchStack.axis = .vertical // stackVer.axis = .vertical
switchStack.distribution = .equalCentering
switchStack.alignment = .leading
switchStack.spacing = 20
let stack = UIStackView(arrangedSubviews: [switchStack, deleteButton])
stack.axis = .vertical
stack.distribution = .equalSpacing
stack.alignment = .center
stack.spacing = 20
addSubview(stack)
stack.anchor(top: safeAreaLayoutGuide.topAnchor, leading: leadingAnchor, bottom: safeAreaLayoutGuide.bottomAnchor, trailing: trailingAnchor, paddingTop: 20, paddingLeft: 10, paddingBottom: 20, paddingRight: 10, width: 0, height: 0)
}
#objc func deleteHandler() {
guard let delegate = delegate else { return }
delegate.removeAllFavorites()
}
#objc func switchChanged(mySwitch: UISwitch) {
print("changed")
}
}
You need 1 delegate and differentiate with a tag
notifView = RowView(title: "Show day's quote as a notification", isOn: false)
alertView = RowView(title: "Ring an alert when notification is played", isOn: false)
notifView.tag = 10
alertView.tag = 11
--- you can do
notifView.addGestureRecognizer(UITapGestureRecognizer(target: self.delegate!, action: #selector(self.delegate!.methodClick)))
Related
Problem
I am trying to do everything programmatically, so I have created this ResultController. For some reason, two of my buttons work but the other three don't work. The program doesn't even notice the touch-up event. I have read many posts on this topic but none of the solutions worked for me.
The two buttons that work are the language button and the swap button, which are embedded in a stack view towards the top of the screen. The 3 buttons that don't work are the soundButton, loopButton, and addFlashcardButton on the result card shown in the image below.
Question
What do I need to do such that these buttons respond to touch events?
Here is the code for this viewController, as well as a picture of the result. I will post the important parts and the whole code for reference. The Buttons are laid out in two functions, setupResultsView() and setupScrollView():
Button Instantiation
var soundButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "speaker.3.fill"), for: .normal)
button.backgroundColor = .white
button.tintColor = .black
button.addTarget(self, action: #selector(soundButtonPressed), for: .touchUpInside)
return button
}()
var loopButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "repeat"), for: .normal)
button.backgroundColor = .white
button.tintColor = .black
button.addTarget(self, action: #selector(loopButtonPressed), for: .touchUpInside)
return button
}()
var addFlashcardButton: UIButton = {
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
button.setImage(UIImage(systemName: "plus"), for: .normal)
button.backgroundColor = K.Colors.purple
button.tintColor = .white
button.roundCorners(cornerRadius: 15)
button.addTarget(self, action: #selector(addFlashcardButtonPressed), for: .touchUpInside)
return button
}()
Selector Functions
#objc func soundButtonPressed() {
print("soundButton")
}
#objc func loopButtonPressed() {
print("loopButton")
}
#objc func addFlashcardButtonPressed() {
print("adding flashcard")
}
Complete Code
import UIKit
class ResultsController: UIViewController {
//MARK: - Info
let cellID = "SoundItOutCell"
//MARK: - Views
var centerTitle: UILabel = {
let label = UILabel(frame: CGRect(x: 10, y: 0, width: 50, height: 40))
label.backgroundColor = K.Colors.purple
label.font = UIFont.boldSystemFont(ofSize: 30)
label.text = "Aura"
label.numberOfLines = 2
label.textColor = .white
label.textAlignment = .center
return label
}()
var languageBarView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.setUnderlineStyle(color: K.Colors.purple)
return view
}()
var langStackView: UIStackView = {
let stackView = UIStackView()
stackView.backgroundColor = .white
stackView.alignment = .center
stackView.distribution = .fillEqually
return stackView
}()
var languageButton: UIButton = {
let button = UIButton()
button.setTitle("Japanese", for: .normal)
button.addTarget(self, action: #selector(languageButtonPressed), for: .touchUpInside)
button.backgroundColor = .white
button.setTitleColor(UIColor.systemBlue, for: .normal)
return button
}()
var swapButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "repeat"), for: .normal)
button.addTarget(self, action: #selector(swapButtonPressed), for: .touchUpInside)
button.tintColor = .black
button.backgroundColor = .white
return button
}()
var englishLabel: UILabel = {
let label = UILabel()
label.text = "English"
label.textColor = .black
label.backgroundColor = .white
label.textAlignment = .center
return label
}()
var textViewBackgroundView = UIView()
var textView: UITextView = {
let textView = UITextView()
textView.returnKeyType = .search
textView.text = "Enter text"
textView.textColor = .lightGray
textView.font = .systemFont(ofSize: 20)
textView.textContainerInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 50)
return textView
}()
var cancelButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "multiply"), for: .normal)
button.backgroundColor = .white
button.tintColor = .black
button.addTarget(self, action: #selector(cancelButtonPressed), for: .touchUpInside)
button.isHidden = true
return button
}()
var scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.backgroundColor = K.Colors.lightGrey
return scrollView
}()
var contentView: UIView = {
let view = UIView()
view.backgroundColor = K.Colors.lightGrey
return view
}()
var resultCardView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.roundCorners(cornerRadius: 10)
return view
}()
var topLabel = UILabel()
var bottomLabel = UILabel()
var soundButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "speaker.3.fill"), for: .normal)
button.backgroundColor = .white
button.tintColor = .black
button.addTarget(self, action: #selector(soundButtonPressed), for: .touchUpInside)
return button
}()
var loopButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "repeat"), for: .normal)
button.backgroundColor = .white
button.tintColor = .black
button.addTarget(self, action: #selector(loopButtonPressed), for: .touchUpInside)
return button
}()
var lineDividerView: UIView = {
let view = UIView()
view.backgroundColor = K.Colors.purple
return view
}()
var addFlashcardButton: UIButton = {
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
button.setImage(UIImage(systemName: "plus"), for: .normal)
button.backgroundColor = K.Colors.purple
button.tintColor = .white
button.roundCorners(cornerRadius: 15)
button.addTarget(self, action: #selector(addFlashcardButtonPressed), for: .touchUpInside)
return button
}()
// Background Views
let resultBackgroundView = UIView ()
let addFlashcardBackgroundView = UIView()
//MARK: - Class Functions
override func viewDidLoad() {
super.viewDidLoad()
setupView()
view.backgroundColor = K.Colors.lightGrey
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
setupShadows()
}
}
extension ResultsController {
//MARK:- View Setup Functions
func setupView() {
self.setupToHideKeyboardOnTapOnView()
setupNavBar()
setupLanguageSelectionView()
setupTextView()
setupScrollView()
}
func setupShadows() {
textViewBackgroundView.setShadow(color: UIColor.black, opacity: 0.3, offset: .init(width: 0, height: 3), radius: 2)
resultBackgroundView.setShadow(color: .black, opacity: 0.3, offset: CGSize(width: 5, height: 5), radius: 2, cornerRadius: 10)
addFlashcardBackgroundView.setShadow(color: .black, opacity: 0.5, offset: CGSize(width: 2, height: 2), radius: 2, cornerRadius: 15)
}
func setupNavBar() {
// Add Center Title
self.navigationItem.titleView = centerTitle
// Add userbutton
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "person"),
style: .plain,
target: self,
action: #selector(profileButtonTapped))
// Make bar color purple, and buttons white
self.navigationController?.navigationBar.tintColor = .white
self.navigationController?.navigationBar.barTintColor = K.Colors.purple
}
func setupLanguageSelectionView() {
// Add Language Bar View
view.addSubview(languageBarView)
languageBarView.anchor(top: view.safeAreaLayoutGuide.topAnchor,
bottom: nil, leading: view.leadingAnchor,
trailing: view.trailingAnchor,
height: 60,
width: nil)
// Add Stack View
languageBarView.addSubview(langStackView)
langStackView.anchor(top: languageBarView.topAnchor,
bottom: languageBarView.bottomAnchor,
leading: languageBarView.leadingAnchor,
trailing: languageBarView.trailingAnchor,
height: nil,
width: nil,
padding: UIEdgeInsets(top: 0, left: 0, bottom: -2, right: 0))
// Add Language Button
langStackView.addArrangedSubview(languageButton)
// Add Swap Button
langStackView.addArrangedSubview(swapButton)
// Add English Label
langStackView.addArrangedSubview(englishLabel)
}
func setupTextView() {
// Add View
view.addSubview(textViewBackgroundView)
textViewBackgroundView.anchor(top: langStackView.bottomAnchor,
bottom: nil,
leading: view.leadingAnchor,
trailing: view.trailingAnchor,
height: 60,
width: nil,
padding: UIEdgeInsets(top: 2, left: 0, bottom: 0, right: 0))
// Add TextView
textView.delegate = self
view.addSubview(textView)
textView.anchor(top: textViewBackgroundView.topAnchor,
bottom: textViewBackgroundView.bottomAnchor,
leading: textViewBackgroundView.leadingAnchor,
trailing: textViewBackgroundView.trailingAnchor,
height: nil,
width: nil,
padding: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0))
// Add X Button
view.addSubview(cancelButton)
cancelButton.anchor(top: textView.topAnchor,
bottom: nil,
leading: nil,
trailing: textView.trailingAnchor,
height: 40,
width: 40,
padding: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0))
}
func setupScrollView() {
// Add scroll view and content view
view.addSubview(scrollView)
view.sendSubviewToBack(scrollView)
scrollView.addSubview(contentView)
// Anchor Scroll View
scrollView.anchorXCenter(top: textView.topAnchor,
bottom: view.safeAreaLayoutGuide.bottomAnchor,
centerAnchor: view.centerXAnchor,
width: view.widthAnchor,
height: nil)
// Anchor Content View
contentView.anchorXCenter(top: scrollView.topAnchor,
bottom: scrollView.bottomAnchor,
centerAnchor: scrollView.centerXAnchor,
width: scrollView.widthAnchor,
height: nil)
// Setup Results Background
contentView.addSubview(resultBackgroundView)
resultBackgroundView.anchorXCenter(top: contentView.topAnchor,
bottom: nil,
centerAnchor: contentView.centerXAnchor,
width: contentView.widthAnchor,
height: nil,
padding: UIEdgeInsets(top: 90, left: 0, bottom: 0, right: 0),
widthOffset: -40)
// Setup Results Card
resultBackgroundView.addSubview(resultCardView)
resultCardView.anchorXCenter(top: resultBackgroundView.topAnchor,
bottom: resultBackgroundView.bottomAnchor,
centerAnchor: resultBackgroundView.centerXAnchor,
width: resultBackgroundView.widthAnchor,
height: nil)
setupResultsView(resultCardView)
}
func setupResultsView(_ resultCardView: UIView) {
let searchInput = "Hello"
let searchOutput = "Yo what's up dog congratulations"
// Configure Top Label
topLabel.attributedText = NSAttributedString(string: searchInput)
topLabel.configureBasedOnInput()
// Configure Bottom Label
bottomLabel.attributedText = NSAttributedString(string: searchOutput)
bottomLabel.configureBasedOnInput()
// Add Flashcard Button to its Background View
addFlashcardBackgroundView.addSubview(addFlashcardButton)
addFlashcardButton.anchor(top: addFlashcardBackgroundView.topAnchor,
bottom: addFlashcardBackgroundView.bottomAnchor,
leading: addFlashcardBackgroundView.leadingAnchor,
trailing: addFlashcardBackgroundView.trailingAnchor,
height: nil,
width: nil)
// Add subviews to Result Card
resultCardView.addSubview(topLabel)
resultCardView.addSubview(bottomLabel)
resultCardView.addSubview(soundButton)
resultCardView.addSubview(loopButton)
resultCardView.addSubview(lineDividerView)
resultCardView.addSubview(addFlashcardBackgroundView)
// Autolayout Constraints For Result Card Subviews
topLabel.translatesAutoresizingMaskIntoConstraints = false
topLabel.topAnchor.constraint(equalTo: resultCardView.topAnchor, constant: 10).isActive = true
topLabel.centerXAnchor.constraint(equalTo: resultCardView.centerXAnchor, constant: 0).isActive = true
topLabel.leadingAnchor.constraint(greaterThanOrEqualTo: resultCardView.leadingAnchor, constant: 10).isActive = true
topLabel.trailingAnchor.constraint(lessThanOrEqualTo: resultCardView.trailingAnchor, constant: -10).isActive = true
lineDividerView.anchor(top: topLabel.bottomAnchor,
bottom: nil, leading: resultCardView.leadingAnchor,
trailing: resultCardView.trailingAnchor,
height: 1,
width: nil,
padding: UIEdgeInsets(top: 10, left: 0, bottom: 0, right: 0))
loopButton.anchor(top: lineDividerView.bottomAnchor,
bottom: nil,
leading: nil,
trailing: resultCardView.trailingAnchor,
height: nil,
width: nil,
padding: UIEdgeInsets(top: 10, left: 0, bottom: 0, right: -10))
soundButton.anchor(top: lineDividerView.bottomAnchor,
bottom: nil,
leading: nil,
trailing: loopButton.leadingAnchor,
height: nil,
width: nil,
padding: UIEdgeInsets(top: 10, left: 0, bottom: 0, right: -10))
bottomLabel.translatesAutoresizingMaskIntoConstraints = false
bottomLabel.topAnchor.constraint(equalTo: soundButton.bottomAnchor, constant: 10).isActive = true
bottomLabel.centerXAnchor.constraint(equalTo: resultCardView.centerXAnchor, constant: 0).isActive = true
bottomLabel.leadingAnchor.constraint(greaterThanOrEqualTo: resultCardView.leadingAnchor, constant: 10).isActive = true
bottomLabel.trailingAnchor.constraint(lessThanOrEqualTo: resultCardView.trailingAnchor, constant: -10).isActive = true
addFlashcardBackgroundView.anchor(top: bottomLabel.bottomAnchor,
bottom: resultCardView.bottomAnchor,
leading: nil,
trailing: resultCardView.trailingAnchor,
height: 30,
width: 30,
padding: UIEdgeInsets(top: 10, left: 0, bottom: -10, right: -10))
}
//MARK:- Selector Functions
#objc func profileButtonTapped() {
print(0)
}
#objc func languageButtonPressed() {
print(1)
}
#objc func swapButtonPressed() {
print(2)
}
#objc func cancelButtonPressed() {
textView.text = ""
textView.textColor = .lightGray
textView.text = "Enter text"
cancelButton.isHidden = true
}
#objc func soundButtonPressed() {
print("soundButton")
}
#objc func loopButtonPressed() {
print("loopButton")
}
#objc func addFlashcardButtonPressed() {
print("adding flashcard")
}
}
Output: Circled Buttons are ones that don't work
So I have a comments feature in my app that users use to communicate. Each comment has a pic, some text,a button that lets you either flag or reply to comments, and the how long ago the comment was posted. As seen in the picture below
However as seen in the picture there is a clear problem. Depending on the amount of text typically anything that spans one line or more. The time label does not show.
import Foundation
import UIKit
import Firebase
protocol CommentCellDelegate: class {
func optionsButtonTapped(cell: CommentCell)
func handleProfileTransition(tapGesture: UITapGestureRecognizer)
}
class CommentCell: UICollectionViewCell {
weak var delegate: CommentCellDelegate? = nil
override var reuseIdentifier : String {
get {
return "cellID"
}
set {
// nothing, because only red is allowed
}
}
var didTapOptionsButtonForCell: ((CommentCell) -> Void)?
weak var comment: CommentGrabbed?{
didSet{
guard let comment = comment else{
return
}
// print("apples")
// textLabel.text = comment.content
//shawn was also here
profileImageView.loadImage(urlString: comment.user.profilePic!)
// print(comment.user.username)
let attributedText = NSMutableAttributedString(string: comment.user.username!, attributes: [NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 14)])
attributedText.append(NSAttributedString(string: " " + (comment.content), attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 14)]))
attributedText.append(NSAttributedString(string: "\n\n", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 4)]))
let timeAgoDisplay = comment.creationDate.timeAgoDisplay()
attributedText.append(NSAttributedString(string: timeAgoDisplay, attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 12), NSAttributedStringKey.foregroundColor: UIColor.gray]))
textView.attributedText = attributedText
}
}
lazy var textView: UITextView = {
let textView = UITextView()
textView.font = UIFont.systemFont(ofSize: 14)
textView.isScrollEnabled = false
textView.textContainer.maximumNumberOfLines = 2
textView.textContainer.lineBreakMode = .byCharWrapping
textView.isEditable = false
return textView
}()
lazy var profileImageView: CustomImageView = {
let iv = CustomImageView()
iv.clipsToBounds = true
iv.isUserInteractionEnabled = true
iv.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleProfileTransition)))
iv.contentMode = .scaleAspectFill
return iv
}()
lazy var flagButton: UIButton = {
let flagButton = UIButton(type: .system)
flagButton.setImage(#imageLiteral(resourceName: "icons8-Info-64"), for: .normal)
flagButton.addTarget(self, action: #selector(optionsButtonTapped), for: .touchUpInside)
return flagButton
}()
#objc func optionsButtonTapped (){
didTapOptionsButtonForCell?(self)
}
#objc func onOptionsTapped() {
delegate?.optionsButtonTapped(cell: self)
}
#objc func handleProfileTransition(tapGesture: UITapGestureRecognizer){
delegate?.handleProfileTransition(tapGesture: tapGesture)
// print("Tapped image")
}
override init(frame: CGRect){
super.init(frame: frame)
addSubview(textView)
addSubview(profileImageView)
addSubview(flagButton)
textView.anchor(top: topAnchor, left: profileImageView.rightAnchor, bottom: bottomAnchor, right: flagButton.leftAnchor, paddingTop: 4, paddingLeft: 4, paddingBottom: 4, paddingRight: 4, width: 0, height: 0)
profileImageView.anchor(top: topAnchor, left: leftAnchor, bottom: nil, right: nil, paddingTop: 8, paddingLeft: 8, paddingBottom: 0, paddingRight: 0, width: 40, height: 40)
profileImageView.layer.cornerRadius = 40/2
flagButton.anchor(top: topAnchor, left: nil, bottom: nil, right: rightAnchor, paddingTop: 4, paddingLeft: 0, paddingBottom: 0, paddingRight: 4, width: 40, height: 40)
flagButton.addTarget(self, action: #selector(CommentCell.onOptionsTapped), for: .touchUpInside)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I figured it was my constraints but it doesn't seem like it is. Does anyone have any idea why this would happen?
Autosizing works only when maximumNumberOfLines is zero.
textView.textContainer.maximumNumberOfLines = 0
Refer: Docs
I am having a little problem with my CommentsViewController that is being implemented using the IGListKit. I have created a cell that is being used to render the comments. Each cell contains a picture, the text, and a button that lets you flag inappropriate comments or reply to comments similar to how it is done in instagram.
This is the CollectionVIewCell
import Foundation
import UIKit
import Firebase
protocol CommentCellDelegate: class {
func optionsButtonTapped(cell: CommentCell)
func handleProfileTransition(tapGesture: UITapGestureRecognizer)
}
class CommentCell: UICollectionViewCell {
weak var delegate: CommentCellDelegate? = nil
override var reuseIdentifier : String {
get {
return "cellID"
}
set {
// nothing, because only red is allowed
}
}
var didTapOptionsButtonForCell: ((CommentCell) -> Void)?
var comment: CommentGrabbed?{
didSet{
guard let comment = comment else{
return
}
// print("apples")
// textLabel.text = comment.content
//shawn was also here
profileImageView.loadImage(urlString: comment.user.profilePic!)
// print(comment.user.username)
let attributedText = NSMutableAttributedString(string: comment.user.username!, attributes: [NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 14)])
attributedText.append(NSAttributedString(string: " " + (comment.content), attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 14)]))
textView.attributedText = attributedText
}
}
let textView: UITextView = {
let textView = UITextView()
textView.font = UIFont.systemFont(ofSize: 14)
textView.isScrollEnabled = false
textView.isEditable = false
return textView
}()
lazy var profileImageView: CustomImageView = {
let iv = CustomImageView()
iv.clipsToBounds = true
iv.isUserInteractionEnabled = true
iv.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleProfileTransition)))
iv.contentMode = .scaleAspectFill
return iv
}()
lazy var flagButton: UIButton = {
let flagButton = UIButton(type: .system)
flagButton.setImage(#imageLiteral(resourceName: "icons8-Info-64"), for: .normal)
flagButton.addTarget(self, action: #selector(optionsButtonTapped), for: .touchUpInside)
return flagButton
}()
#objc func optionsButtonTapped (){
didTapOptionsButtonForCell?(self)
}
#objc func onOptionsTapped() {
delegate?.optionsButtonTapped(cell: self)
}
#objc func handleProfileTransition(tapGesture: UITapGestureRecognizer){
delegate?.handleProfileTransition(tapGesture: tapGesture)
// print("Tapped image")
}
override init(frame: CGRect){
super.init(frame: frame)
addSubview(textView)
addSubview(profileImageView)
addSubview(flagButton)
textView.anchor(top: topAnchor, left: profileImageView.rightAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 4, paddingLeft: 4, paddingBottom: 4, paddingRight: 4, width: 0, height: 0)
profileImageView.anchor(top: topAnchor, left: leftAnchor, bottom: nil, right: nil, paddingTop: 8, paddingLeft: 8, paddingBottom: 0, paddingRight: 0, width: 40, height: 40)
profileImageView.layer.cornerRadius = 40/2
flagButton.anchor(top: topAnchor, left: nil, bottom: nil, right: rightAnchor, paddingTop: 4, paddingLeft: 0, paddingBottom: 0, paddingRight: 4, width: 40, height: 40)
flagButton.addTarget(self, action: #selector(CommentCell.onOptionsTapped), for: .touchUpInside)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Now this controller has a delegate method that allows me to do some operation on click of the button. It allows me to present an alertcontroller which asks either reply or flag.
When I click reply I want the textfield to autoGenerate with #someUserName like how Instagram does and then I type some text, hit reply, and send the comment.
Below is my CommentSectionController
import UIKit
import IGListKit
import Foundation
import Firebase
protocol CommentsSectionDelegate: class {
func CommentSectionUpdared(sectionController: CommentsSectionController)
}
class CommentsSectionController: ListSectionController,CommentCellDelegate {
weak var delegate: CommentsSectionDelegate? = nil
var comment: CommentGrabbed?
let userProfileController = ProfileeViewController(collectionViewLayout: UICollectionViewFlowLayout())
var eventKey: String?
override init() {
super.init()
// supplementaryViewSource = self
//sets the spacing between items in a specfic section controller
inset = UIEdgeInsets(top: 5, left: 0, bottom: 0, right: 0)
}
// MARK: IGListSectionController Overrides
override func numberOfItems() -> Int {
return 1
}
override func sizeForItem(at index: Int) -> CGSize {
let frame = CGRect(x: 0, y: 0, width: collectionContext!.containerSize.width, height: 50)
let dummyCell = CommentCell(frame: frame)
dummyCell.comment = comment
dummyCell.layoutIfNeeded()
let targetSize = CGSize(width: collectionContext!.containerSize.width, height: 55)
let estimatedSize = dummyCell.systemLayoutSizeFitting(targetSize)
let height = max(40+8+8, estimatedSize.height)
return CGSize(width: collectionContext!.containerSize.width, height: height)
}
override var minimumLineSpacing: CGFloat {
get {
return 0.0
}
set {
self.minimumLineSpacing = 0.0
}
}
override func cellForItem(at index: Int) -> UICollectionViewCell {
guard let cell = collectionContext?.dequeueReusableCell(of: CommentCell.self, for: self, at: index) as? CommentCell else {
fatalError()
}
// print(comment)
cell.comment = comment
cell.delegate = self
return cell
}
override func didUpdate(to object: Any) {
comment = object as? CommentGrabbed
}
override func didSelectItem(at index: Int){
}
func optionsButtonTapped(cell: CommentCell){
print("like")
let comment = self.comment
_ = comment?.uid
// 3
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
// 4
if comment?.uid != User.current.uid {
let flagAction = UIAlertAction(title: "Report as Inappropriate", style: .default) { _ in
ChatService.flag(comment!)
let okAlert = UIAlertController(title: nil, message: "The post has been flagged.", preferredStyle: .alert)
okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
self.viewController?.present(okAlert, animated: true, completion: nil)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
let replyAction = UIAlertAction(title: "Reply to Comment", style: .default, handler: { (_) in
//do something here later to facilitate reply comment functionality
print("Attempting to reply to user \(comment?.user.username) comment")
})
alertController.addAction(replyAction)
alertController.addAction(cancelAction)
alertController.addAction(flagAction)
}else{
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
let deleteAction = UIAlertAction(title: "Delete Comment", style: .default, handler: { _ in
ChatService.deleteComment(comment!, (comment?.eventKey)!)
let okAlert = UIAlertController(title: nil, message: "Comment Has Been Deleted", preferredStyle: .alert)
okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
self.viewController?.present(okAlert, animated: true, completion: nil)
self.onItemDeleted()
})
alertController.addAction(cancelAction)
alertController.addAction(deleteAction)
}
self.viewController?.present(alertController, animated: true, completion: nil)
}
func onItemDeleted() {
delegate?.CommentSectionUpdared(sectionController: self)
}
func handleProfileTransition(tapGesture: UITapGestureRecognizer){
userProfileController.user = comment?.user
if Auth.auth().currentUser?.uid != comment?.uid{
self.viewController?.present(userProfileController, animated: true, completion: nil)
}else{
//do nothing
}
}
}
Now my textView that I utilize for writing comments is in another file
import UIKit
protocol CommentInputAccessoryViewDelegate {
func handleSubmit(for comment: String?)
}
class CommentInputAccessoryView: UIView, UITextViewDelegate {
var delegate: CommentInputAccessoryViewDelegate?
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
fileprivate let submitButton: UIButton = {
let submitButton = UIButton(type: .system)
submitButton.setTitle("Submit", for: .normal)
submitButton.setTitleColor(.black, for: .normal)
submitButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
submitButton.addTarget(self, action: #selector(handleSubmit), for: .touchUpInside)
//submitButton.isEnabled = false
return submitButton
}()
lazy var commentTextView: CommentInputTextView = {
let textView = CommentInputTextView()
// textView.placeholder = "Add a comment"
textView.delegate = self
textView.isScrollEnabled = false
textView.backgroundColor = .white
textView.font = UIFont.boldSystemFont(ofSize: 15)
textView.textContainer.lineBreakMode = .byWordWrapping
// textView.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
return textView
}()
override init(frame: CGRect) {
super.init(frame: frame)
// backgroundColor = .red
//1
autoresizingMask = .flexibleHeight
addSubview(submitButton)
submitButton.anchor(top: topAnchor, left: nil, bottom: bottomAnchor, right:rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 12, width: 50, height: 0)
addSubview(commentTextView)
//3
if #available(iOS 11.0, *){
commentTextView.anchor(top: topAnchor, left: leftAnchor, bottom: safeAreaLayoutGuide.bottomAnchor, right: submitButton.leftAnchor, paddingTop: 8, paddingLeft: 8, paddingBottom: 8, paddingRight: 0, width: 0, height: 0)
}else{
//fallback on earlier versions
}
setupLineSeparatorView()
}
// 2
override var intrinsicContentSize: CGSize {
return .zero
}
fileprivate func setupLineSeparatorView(){
let lineSeparatorView = UIView()
lineSeparatorView.backgroundColor = UIColor.rgb(red: 230, green: 230, blue: 230)
addSubview(lineSeparatorView)
lineSeparatorView.anchor(top:topAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0.5)
}
#objc func handleSubmit(){
guard let commentText = commentTextView.text else{
return
}
delegate?.handleSubmit(for: commentText)
}
#objc func textFieldDidChange(_ textField: UITextView) {
let isCommentValid = commentTextView.text?.count ?? 0 > 0
if isCommentValid {
submitButton.isEnabled = true
}else{
submitButton.isEnabled = false
}
}
func clearCommentTextField(){
commentTextView.text = nil
commentTextView.showPlaceholderLabel()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
My question is how would I auto populate the textview from my commentsSectionController using IGListKit and keeping the same structure because I don't have access to it from my commentsSectionController
I have a textview instead of a textfield so I can autowrap text. However, the text itself is not auto wrapping. When the line gets too long the following text just disappears. If I go to an earlier point in the line and start deleting text, then the text that wasn't visible begins to appear.It should autoresize itself butit does not.
import UIKit
protocol CommentInputAccessoryViewDelegate {
func handleSubmit(for comment: String?)
}
class CommentInputAccessoryView: UIView, UITextViewDelegate {
var delegate: CommentInputAccessoryViewDelegate?
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
fileprivate let submitButton: UIButton = {
let submitButton = UIButton(type: .system)
submitButton.setTitle("Submit", for: .normal)
submitButton.setTitleColor(.black, for: .normal)
submitButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
submitButton.addTarget(self, action: #selector(handleSubmit), for: .touchUpInside)
//submitButton.isEnabled = false
return submitButton
}()
lazy var commentTextView: UITextView = {
let textView = UITextView()
// textView.placeholder = "Add a comment"
textView.delegate = self
textView.isScrollEnabled = false
textView.backgroundColor = .red
textView.font = UIFont.boldSystemFont(ofSize: 12)
textView.textContainer.lineBreakMode = .byWordWrapping
// textView.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
return textView
}()
override init(frame: CGRect) {
super.init(frame: frame)
// backgroundColor = .red
//1
autoresizingMask = .flexibleHeight
addSubview(submitButton)
submitButton.anchor(top: topAnchor, left: nil, bottom: bottomAnchor, right:rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 12, width: 50, height: 0)
addSubview(commentTextView)
//3
if #available(iOS 11.0, *){
commentTextView.anchor(top: topAnchor, left: leftAnchor, bottom: safeAreaLayoutGuide.bottomAnchor, right: submitButton.leftAnchor, paddingTop: 0, paddingLeft: 12, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
}else{
//fallback on earlier versions
}
setupLineSeparatorView()
}
//2
override var intrinsicContentSize: CGSize{
return .zero
}
fileprivate func setupLineSeparatorView(){
let lineSeparatorView = UIView()
lineSeparatorView.backgroundColor = UIColor.rgb(red: 230, green: 230, blue: 230)
addSubview(lineSeparatorView)
lineSeparatorView.anchor(top:topAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0.5)
}
#objc func handleSubmit(){
guard let commentText = commentTextView.text else{
return
}
delegate?.handleSubmit(for: commentText)
}
#objc func textFieldDidChange(_ textField: UITextView) {
let isCommentValid = commentTextView.text?.count ?? 0 > 0
if isCommentValid {
submitButton.isEnabled = true
}else{
submitButton.isEnabled = false
}
}
func clearCommentTextField(){
commentTextView.text = nil
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I'm thinking it is the anchors but I'm not entirely sure.
I have a uiview with a couple of elements that I use for a comment function in my app. There is a text field, button, and line separator. Everything renders fine however when I click submit the app crashes and I get this error.
'NSInvalidArgumentException', reason: '-[UIButton copyWithZone:]: unrecognized selector sent to instance 0x7fe58c459620'
I don't see anything wrong with my implementation so this error is a little confusing to me. This is the class for my UIView
import UIKit
protocol CommentInputAccessoryViewDelegate {
func handleSubmit(for comment: String?)
}
class CommentInputAccessoryView: UIView, UITextFieldDelegate {
var delegate: CommentInputAccessoryViewDelegate?
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
fileprivate let submitButton: UIButton = {
let submitButton = UIButton(type: .system)
submitButton.setTitle("Submit", for: .normal)
submitButton.setTitleColor(.black, for: .normal)
submitButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
submitButton.addTarget(self, action: #selector(handleSubmit), for: .touchUpInside)
//submitButton.isEnabled = false
return submitButton
}()
lazy var commentTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "Add a comment"
textField.delegate = self
textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
return textField
}()
override init(frame: CGRect) {
super.init(frame: frame)
// backgroundColor = .red
addSubview(submitButton)
submitButton.anchor(top: topAnchor, left: nil, bottom: bottomAnchor, right:rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 12, width: 50, height: 0)
addSubview(commentTextField)
commentTextField.anchor(top: topAnchor, left: leftAnchor, bottom: bottomAnchor, right: submitButton.leftAnchor, paddingTop: 0, paddingLeft: 12, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
setupLineSeparatorView()
}
fileprivate func setupLineSeparatorView(){
let lineSeparatorView = UIView()
lineSeparatorView.backgroundColor = UIColor.rgb(red: 230, green: 230, blue: 230)
addSubview(lineSeparatorView)
lineSeparatorView.anchor(top:topAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0.5)
}
#objc func handleSubmit(for comment: String?){
guard let commentText = commentTextField.text else{
return
}
delegate?.handleSubmit(for: commentText)
}
#objc func textFieldDidChange(_ textField: UITextField) {
let isCommentValid = commentTextField.text?.count ?? 0 > 0
if isCommentValid {
submitButton.isEnabled = true
}else{
submitButton.isEnabled = false
}
}
func clearCommentTextField(){
commentTextField.text = nil
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is the accompanying class that ultimately handles the submission through a protocol method
//allows you to gain access to the input accessory view that each view controller has for inputting text
lazy var containerView: CommentInputAccessoryView = {
let frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 50)
let commentInputAccessoryView = CommentInputAccessoryView(frame:frame)
commentInputAccessoryView.delegate = self
return commentInputAccessoryView
}()
#objc func handleSubmit(for comment: String?){
guard let comment = comment, comment.count > 0 else{
return
}
let userText = Comments(content: comment, uid: User.current.uid, profilePic: User.current.profilePic!,eventKey: eventKey)
sendMessage(userText)
// will clear the comment text field
self.containerView.clearCommentTextField()
}
extension NewCommentsViewController {
func sendMessage(_ message: Comments) {
ChatService.sendMessage(message, eventKey: eventKey)
}
}
The associated method for the target/action #selector(handleSubmit) must be
#objc func handleSubmit(_ sender: UIButton) { ...
or
#objc func handleSubmit() { ...
Other forms are not supported.
Does the code compile at all?
Actually you can't use self in the initializer let submitButton: UIButton = { .. }()
The problem seems to be that UIButton doesn't have a copyWithZone method and that you can't define delegates for UIButtons:
what are the delegate methods available with uibutton