#swift #class #uibutton #selector #swift5
#swift #класс #уибуттон #селектор #swift5
Вопрос:
Я пытаюсь создать класс DropDownMenu, но когда я пытаюсь вызвать addTarget для одной из кнопок, появляется эта ошибка.
нераспознанный селектор, отправленный экземпляру 0x7fd167f06120′, завершается неперехваченным исключением типа NSException
Я был бы очень признателен за любую помощь, и ни один ответ не является плохим!
Вот весь мой класс
class DropDownMenu: UIView {
// Main button or Pre
var main: UIButton! = UIButton(frame: CGRect(x: 0, y: 0, width: 46, height: 30))
var view: UIView!
// Title
var buttonTitles: [String]! = [""]
var titleColor: UIColor! = UIColor.black
var font: UIFont! = UIFont.systemFont(ofSize: 18)
// Individual Button
var buttonsBorderWidth: CGFloat! = 0
var buttonsBorderColor: UIColor? = UIColor.white
var buttonsCornerRadius: CGFloat! = 0
var color: UIColor! = UIColor.clear
// Button Images
var buttonsImageEdgeInsets: UIEdgeInsets? = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
var images: [UIImage]? = nil
// Onclick stuff
var target: UIViewController!
private var currentSelected: String? = nil
private var optionsStack = UIStackView()
init(main: UIButton) {
self.main = main
super.init(frame: CGRect())
}
func createDropDownMenu() {
main.addTarget(target, action: #selector(DropDownMenu.openDropdown(_:)), for: .touchUpInside)
print("Button Target?: (main.allTargets), self.target: (String(describing: target))")
let mainFrame = main.frame
optionsStack.frame = CGRect(x: mainFrame.minX, y: mainFrame.maxY, width: mainFrame.width, height: CGFloat(buttonTitles.count) * mainFrame.height)
optionsStack.axis = .vertical
view.addSubview(optionsStack)
var y: CGFloat! = 0
for title in buttonTitles {
let button = UIButton(frame: CGRect(x: 0, y: y, width: mainFrame.width, height: mainFrame.height))
button.setTitle(title, for: .normal)
button.setTitleColor(titleColor, for: .normal)
button.backgroundColor = color
button.titleLabel?.font = font
button.addTarget(target, action: #selector(DropDownMenu.onclick), for: .touchUpInside)
y = mainFrame.height
optionsStack.addArrangedSubview(button)
}
for button in optionsStack.arrangedSubviews {
button.isHidden = true
button.alpha = 0
}
}
@objc private func openDropdown(_ sender: UIButton) {
print("sender: (String(describing: sender))")
optionsStack.arrangedSubviews.forEach { (button) in
UIView.animate(withDuration: 0.7) {
button.isHidden = !button.isHidden
button.alpha = button.alpha == 0 ? 1 : 0
self.view.layoutIfNeeded()
}
}
}
@objc private func onclick(_ sender: UIButton) {
let title = sender.titleLabel!.text
print(title as Any)
main.setTitle(title, for: .normal)
optionsStack.arrangedSubviews.forEach { (button) in
UIView.animate(withDuration: 0.7) {
button.isHidden = true
button.alpha = 0
}
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Вот код и создание объекта в ViewController
let grade = UIButton(frame: CGRect(x: 50, y: 300, width: 80, height: 30))
grade.layer.borderWidth = 1
grade.setTitle("Grade", for: .normal)
grade.titleLabel?.font = UIFont.boldSystemFont(ofSize: 20)
grade.setTitleColor(UIColor.black, for: .normal)
let gradeDP = DropDownMenu(main: main)
gradeDP.buttonTitles = ["Title 1", "Title 2", "Title 3"]
gradeDP.color = UIColor.gray
gradeDP.target = self
gradeDP.titleColor = UIColor.white
gradeDP.view = view
view.addSubview(grade)
gradeDP.createDropDownMenu()
Печатается первый оператор print в функции createDropDownMenu()…
Цель кнопки?: [AnyHashable(<HomeworkHelp.DropDownMenu: 0x7ffb555200b0; frame = (0 0; 0 0); layer = <CALayer: 0x600002bdf5c0>>)], self.target: необязательно(<HomeworkHelp.CreateAccountViewController: 0x7ffb5550a7b0>)
После редактирования его с помощью mightknow я придумал этот класс. В нем нет никаких действий onclick для mainButton.
class DropDownMenu: UIStackView {
var options: [String]! = [] // Labels for all of the options
var titleButton: UIButton! = UIButton() // The Main Title Button
init(options: [String]) {
self.options = options
let mainFrame = titleButton.frame
super.init(frame: CGRect(x: mainFrame.minX, y: mainFrame.maxY, width: mainFrame.width, height: mainFrame.height * CGFloat(options.count)))
var y: CGFloat = 0
for title in self.options {
let button = UIButton(frame: CGRect(x: 0, y: y, width: self.frame.width, height: mainFrame.height))
button.setTitle(title, for: .normal)
button.setTitleColor(titleButton.titleLabel?.textColor, for: .normal)
button.backgroundColor = titleButton.backgroundColor
button.addTarget(self, action: #selector(dropDownOptionClicked(_:)), for: .touchUpInside)
button.isHidden = true
button.alpha = 0
self.addArrangedSubview(button)
y = 1
}
}
@objc func openDropDown(_ sender: UIButton) {
print("Open DropDownMenu")
for button in self.arrangedSubviews {
UIView.animate(withDuration: 0.7) {
button.isHidden = !button.isHidden
button.alpha = button.alpha == 0 ? 1 : 0
self.layoutIfNeeded()
self.superview?.layoutIfNeeded()
}
}
}
@objc private func dropDownOptionClicked(_ sender: UIButton) {
print(sender.titleLabel?.text as Any)
}
init() {
super.init(frame: CGRect.zero)
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
И чем является мой ViewController…
let dp = DropDownMenu(options: ["Label 1", "Label 2", "Label 3"])
let titleButton = UIButton(frame: CGRect(x: 100, y: 300, width: 180, height: 40))
titleButton.backgroundColor = UIColor.white
titleButton.setTitle("DropDownMenu", for: .normal)
titleButton.setTitleColor(UIColor.black, for: .normal)
titleButton.layer.borderWidth = 2
titleButton.addTarget(self, action: #selector(dp.openDropDown(_:)), for: .touchUpInside)
dp.titleButton = titleButton
Ошибка …
Цель кнопки?: [AnyHashable(<HomeworkHelp.DropDownMenu: 0x7ffb555200b0; frame = (0 0; 0 0); layer = <CALayer: 0x600002bdf5c0>>)], self.target: необязательно(<HomeworkHelp.CreateAccountViewController: 0x7ffb5550a7b0>)
все еще всплывает, и я понятия не имею, почему.
Комментарии:
1. view.addSubview(main), может быть, вы это пропустили?
2. Привет! Спасибо вам за ответ! Однако я добавил его в свой обзор, так что проблема не в этом
3. можете ли вы попробовать self.openDropDown вместо DropDownMenu.openDropDown
4. К сожалению, это не работает. Спасибо за предложение, хотя!
5. Я также попытался выполнить addTarget в ViewController. Это также вызвало ту же ошибку.
Ответ №1:
Вы устанавливаете цель как a UIViewController
, когда вызываемый вами метод на самом деле является методом DropDownMenu
класса. Что вам нужно сделать, так это установить целевое значение self
вместо target
свойства:
main.addTarget(self, action: #selector(DropDownMenu.openDropdown(_:)), for: .touchUpInside)
РЕДАКТИРОВАТЬ: В ответ на ваш комментарий, вот код, который я использую для его тестирования. Есть несколько вариантов компоновки / цвета, которые я сделал просто для того, чтобы мне было ясно, но это работает:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let main = UIButton(frame: CGRect(x: 0, y: 100, width: 80, height: 30))
main.layer.borderWidth = 1
main.setTitle("Grade", for: .normal)
main.titleLabel?.font = UIFont.boldSystemFont(ofSize: 20)
main.setTitleColor(UIColor.black, for: .normal)
let gradeDP = DropDownMenu(main: main)
gradeDP.buttonTitles = ["Title 1", "Title 2", "Title 3"]
gradeDP.color = UIColor.gray
gradeDP.target = self
gradeDP.titleColor = UIColor.white
gradeDP.view = UIView()
self.view.addSubview(gradeDP)
let b = self.view.bounds
gradeDP.frame = CGRect(x: b.minX, y: b.minY, width: b.width, height: b.height/2)
gradeDP.backgroundColor = UIColor.purple
gradeDP.target = self
gradeDP.addSubview(gradeDP.main)
gradeDP.createDropDownMenu()
}}
Что касается вашего кода, я исхожу из предположения, что код, который вы добавили во второй части вашего вопроса, находится внутри viewDidLoad()
метода вашего ViewController, и что main
переменная, которую вы используете для инициализации DropDownMenu
, является переменной экземпляра вашего ViewController, потому что я не вижу ее больше нигде в области видимости. Если это так, то, безусловно, есть некоторые проблемы. Они есть:
- На самом деле вы никогда не добавляете
gradeDP
в свою иерархию представлений. Если это то, чтоgradeDP.view = view
должна делать строка, это не так. На самом деле этот код устанавливаетview
свойствоgradeDP
как ссылку на свойство ViewControllerview
. И, если в вашем классе DropDownMenu нет кода, который вы не включили, вы на самом деле не используете эту ссылку ни для чего. Таким образом, вы можете полностью избавиться от этой строки иview
свойства в вашемDropDownMenu
классе. Если то, что вы пытаетесь сделать, это установить ViewController view равнымgradeDP
, этот код будетself.view = gradeDP
, но я на самом деле не рекомендую делать это таким образом.UIViewController
Свойство Aview
используется в некоторых специальных функциях, и, вероятно, с ним не следует сильно возиться. Вероятно, вы захотите добавитьgradeDP
в качестве подвида, как я сделал в своем коде выше. grade
Созданная вами кнопка не используется вашим выпадающим меню. Я предполагаю, что вы хотели инициализировать это вместоmain
переменной (которая выходит за рамки вашего кода), например:let gradeDP = DropDownMenu(main: grade)
Короче говоря, если в другом месте нет кода, которым вы не поделились, ваш приведенный выше код создает UIButton с надписью «Grade», который виден, но на самом деле ничего не делает (и не является частью вашего выпадающего меню), и выпадающее меню, которое на самом деле не видно, но будет иметь main
кнопку это говорит openDropdown(_:)
о том, что так оно и было. Я предполагаю, что это не так, как это должно работать. Надеюсь, приведенный выше код поможет вам добраться туда, где вы хотите быть.
Комментарии:
1. Привет! Спасибо за ваш ответ! Выполнение этого устраняет ошибку, но openDropdown() не вызывается.
2. Вы уверены? Тогда это отдельная проблема. Он отлично работает в проекте, который я создал для его тестирования. Однако я немного настроил свой ViewController, чтобы все это произошло. Может быть, мы сделали что-то другое там.
3. Спасибо, что приложили все усилия, чтобы помочь мне! Итак, код, который вы видите, — это весь мой код. Строка «main.addTarget(self, action: #selector(DropDownMenu.openDropdown(_:)), для: .touchUpInside)» находится внутри класса. Я не создавал его в своем ViewController.
4. ОК. Я собираюсь отредактировать свой ответ, чтобы опубликовать код ViewController, который я использовал, чтобы заставить его работать. Я думаю, у вас есть некоторые проблемы с иерархией представлений. Кроме того, я думаю, вы можете рассмотреть возможность размещения как можно большего количества кода настройки внутри инициализатора для вашего класса DropDownMenu, а не в ViewController. Это немного улучшит функциональность, что сделает его менее подверженным ошибкам и повторно используемым на нескольких ViewControllers.
5. Это было бы действительно полезно! Я не уверен, что еще я могу изменить внутри. Все в ViewController — это просто настраиваемые параметры.
Ответ №2:
Что касается предложений по перестройке вашего класса, чтобы он работал должным образом, вы можете начать с чего-то вроде этого:
class DropDownMenu : UIView {
var dropdownOptions : [String] = []
private var titleButton : UIButton = UIButton()
private var optionsStack : UIStackView = UIStackView()
private var optionsButtons : [UIButton] = []
@objc private func openDropdown(_ sender: UIButton) {
// Add code to make dropdown options appear. There are multiple ways of doing this. For instance, the optionsButtons could be hidden and then unhidden when it's clicked, or they could be created only once the button is clicked.
}
@objc private func selectedOption(_ sender: UIButton) {
// Code here for when option is selected
}
init(options: [String]) {
self.dropdownOptions = options
super.init(frame: CGRect.zero)
// Customize all of your subviews here, and add them to your DropDownMenu (as subviews)
// Add openDropdown(_:) target to self.titleButton
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
Большая часть кода, который вы уже написали для своей оригинальной версии класса, может входить в функции там. Кроме того, в вашей оригинальной версии много ненужного кода. Например, target
переменная не используется после устранения исходной ошибки, view
переменная устарела, а createDropDownMenu()
функция не нужна, потому что весь этот код может использоваться либо в функциях init(options:), либо openDropdown(_:) .
Затем, если вы решите создать класс с использованием этого шаблона, вы должны реализовать его в viewDidLoad()
методе вашего ViewController с помощью кода:
let dropdown = DropDownMenu(titles: ["Title 1", "Title 2", "Title 3"])
self.view.addSubview(dropdown)
// Follow that with layout code that ensures it's the proper size and in the proper location
Я надеюсь, что в сочетании с моими комментариями это имеет смысл, полезно и не слишком сложно. Что я рекомендую сделать, так это запустить новый пустой проект (или цель), создать ваш класс и добавить его в ViewController, в котором больше ничего нет. Это хороший способ изолировать его, проверить и убедиться, что все выглядит и работает правильно. В случае, если вам нужно альтернативное предложение о том, как создать свой класс, вы можете попробовать сделать DropDownMenu подклассом UIStackView
(вместо UIView
) с главной кнопкой и всеми кнопками опций, расположенными в подвидах. На самом деле это может быть проще, потому что это как бы отсекает посредника, если хотите, и все, что вам нужно сделать при открытии / закрытии выпадающего списка, — это добавить / удалить представления из .arrangedSubviews
свойства.
Также важно то, что если вашему представлению необходимо передать информацию (например, какой параметр выбран) обратно в ViewController, убедитесь, что ссылка на ViewController отмечена weak
, чтобы вы не создавали цикл сохранения.
В заключение, если вы разочарованы тем, что нет быстрого решения, позволяющего заставить исходный класс работать, и хотите продолжать попытки, возможно, есть какой-то способ собрать решение (например, код из моего первого ответа, который действительно работает …), Но в конечном итоге это вероятно, это только вызовет больше проблем в дальнейшем. Так что, желаю удачи во всем.
Комментарии:
1. Большое вам спасибо за вашу помощь. Я использовал ваш шаблон, но немного изменил его. Я сделал так, чтобы подклассом был UIStackView. Я также удаляю все экземпляры главной кнопки с точки зрения действий с кнопками. Я поместил свой обновленный код в первоначальный вопрос, чтобы вы могли просмотреть его более подробно.
Ответ №3:
Я, наконец, понял это! Целью должно быть выпадающее меню.
titleButton.addTarget(dp, action: #selector(dp.openDropDown(_:)), for: .touchUpInside)
Вот остальная часть кода…
let titleButton = UIButton(frame: CGRect(x: 50, y: 290, width: 100, height: 40))
titleButton.backgroundColor = UIColor.white
titleButton.setTitle("Grade", for: .normal)
titleButton.setTitleColor(UIColor.black, for: .normal)
titleButton.layer.borderWidth = 2
titleButton.layer.cornerRadius = 10
let dp = DropDownMenu(options: ["1", "Freshman", "Sophomore", "Junior", "Senior", "College"])
dp.titleButton = titleButton
dp.target = self
dp.borderWidth = 2
dp.spacing = 5
dp.cornerRadius = 10
dp.bgColor = UIColor.white
Добавление его в подвиды и его создание…
view.addSubview(titleButton)
view.addSubview(dp)
dp.createDropDownMenu()