UIScrollView перекрывает другие элементы UIE, поэтому они больше не активируются - PullRequest
1 голос
/ 08 октября 2019

Уважаемый StackOverflowCommunity,

Я сейчас работаю над проектом, где мне нужно создать полностью динамический пользовательский интерфейс. Для этого я создаю все это программно, чтобы сделать его максимально простым. Теперь я столкнулся с проблемой, которая заключается в том, что как только я обертываю свой contentView (UIStackView) с UIScrollView, чтобы сделать его прокручиваемым, scrollView находится перед всеми другими элементами UIE, так что я могу только прокручивать. Я не могу взаимодействовать с кнопками, ползунками, переключателями или чем-то еще, ни одно событие не будет вызвано.

Я буквально сделал все, что мог придумать (работая над этой проблемой на ДНИ), но не смог найти подходящихответьте, будь то в Google или в переполнении стека или на форумах Apple.

Я уверен, что это довольно небольшое изменение, о котором я просто не мог думать. Я очень ценю любую вашу помощь.

Структура выглядит следующим образом: ViewController > UIScrollView > UIStackView > Item Wrappers (for example contains a UISwitch and a Describing Label) > Single UIElement

При взаимодействии с пользователем (например, при выборе другого режима) оболочки удаляются и / или добавляются в представление. как пользователь нуждается. Я только опубликовал код, который как-то актуален для этой проблемы (на мой взгляд). Если вам нужна дополнительная информация, не стесняйтесь спрашивать.

Мне, возможно, нужно добавить: как только я удаляю UIScrollView и просто добавляю StackView (называемый contentView в коде) к основному представлению, все это прекрасно работает. Я просто не могу прокрутить его, что является большой проблемой, как только к представлению будет добавлено более 5 элементов-обёрток.

    var wrappers : Dictionary<String, UIView> = [:]
var elements : Dictionary<String, [UIView]> = [:]
var constraints : Dictionary = [String: [[NSLayoutConstraint]]]()
let contentView = UIStackView()

let states = [
    "state_operating_hours",
    "state_dim",
    "state_brightness_sensor",
    "state_operating_voltage",
    "state_circuit_voltage",
    "state_load_current_led",
    "state_output_power",
    "state_temperature",
    "state_error",
    "state_sw_version",
    "state_hw_version"
]

let checkboxes = [
    "summertime_wintertime",
    "dali",
    "error_output"
]

let sliders = [
    "immediate_sensitivity",
    "immediate_dawn",
    "immediate_dim",
    "immediate_day",
    "immediate_dusk",
    "immediate_night",
    "aging"
]

let textInputs = [
    "module_name",
    "switch_delay"
]

let dropdowns = [
    "mode",
    "bt_state",
    "config_output"
]

let timePickers = [
    "phase_0",
    "phase_1",
    "phase_2",
    "phase_3"
]

let buttons = [
    "state_trigger",
    "reset_trigger",
]

let l_kind_criteria = [
    "immediate_dawn",
    "immediate_day",
    "immediate_dusk",
    "immediate_night",
    "immediate_sensitivity"
]

let d_kind_criteria = [
    "immediate_dim"
]

let t_kind_criteria = [
    "phase_0",
    "phase_1",
    "phase_2",
    "phase_3"
]

let m_kind_criteria = [
    "immediate_dawn",
    "immediate_day",
    "immediate_dusk",
    "immediate_night",
    "immediate_sensitivity",
    "phase_0",
    "phase_1"
]

let user_criteria = [
    //"access",
    //"state_trigger",
    //"reset_trigger",
    "mode",
    "summertime_wintertime"
]

let service_criteria = [
    "module_name",
    //"access",
    "state_trigger",
    "reset_trigger",
    "mode",
    "bt_state",
    "config_output",
    "aging",
    "switch_delay",
    "summertime_wintertime",
    "error_output",
    "dali"
]

override func viewDidLoad() {
    bleService.delegate = self
    bleService.requestAuthMode()
    view.backgroundColor = .lightGray
    bleService.send(aText: "c28r:#")
    bleService.send(aText: "c05r:#")
    Toast.show(message: "Statuswerte werden abgerufen..." , controller: self)
    buildLayout()
}

// Class - Functions

func buildLayout() {
    // Building the Basic Layout

    let topView = UIView()
    topView.backgroundColor = .purple
    self.view.addSubview(topView)
    topView.translatesAutoresizingMaskIntoConstraints = false

    let logoImageView = UIImageView(image: UIImage(named: "placeholder"))
    logoImageView.translatesAutoresizingMaskIntoConstraints = false
    logoImageView.frame = CGRect(x: 0, y: 0, width: view.frame.width/1.8, height: 30)
    topView.addSubview(logoImageView)

    logoImageView.leftAnchor.constraint(greaterThanOrEqualTo: view.leftAnchor, constant: 20).isActive = true
    logoImageView.topAnchor.constraint(greaterThanOrEqualTo: view.topAnchor, constant: 30).isActive = true

    topView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
    topView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
    topView.heightAnchor.constraint(equalToConstant: view.frame.height/3).isActive = true
    topView.centerYAnchor.constraint(equalTo: view.topAnchor).isActive = true
    topView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true

    //Generate and add Scroll View to Main Window

    let scrollView = UIScrollView()
    scrollView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(scrollView)

    NSLayoutConstraint.activate([
        scrollView.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: 20),
        scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
        scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
        scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        scrollView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
    ])

    //Add Content Stack to Scroll View

    contentView.axis = .vertical
    contentView.alignment = .fill
    contentView.spacing = 150
    contentView.distribution = .fill
    contentView.backgroundColor = .blue
    contentView.translatesAutoresizingMaskIntoConstraints = false
    scrollView.addSubview(contentView)

    NSLayoutConstraint.activate([
        contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
        contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 20),
        contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -20),
        contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
        contentView.widthAnchor.constraint(equalToConstant: scrollView.frame.width),
        contentView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
    ])

    // programmatically creating layout elements without constraints
    // Elements that change a value are always last in their respective array

    for (index, dropdownName) in dropdowns.enumerated() {
        constraints[dropdownName] = [[]]
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = dropdownName

        let leadAnch = label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
        constraints[dropdownName]!.append([leadAnch])

        let textField = UITextField()
        textField.delegate = self
        textField.translatesAutoresizingMaskIntoConstraints = false
        textField.backgroundColor = .white
        textField.layer.cornerRadius = 5

        var trailAnch = textField.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
        var widthAnch = textField.widthAnchor.constraint(equalToConstant: view.frame.width / 6)
        constraints[dropdownName]!.append([trailAnch, widthAnch])

        let pickerView = UIPickerView()
        pickerView.backgroundColor = .white
        pickerView.translatesAutoresizingMaskIntoConstraints = false
        pickerView.delegate = self
        pickerView.isHidden = true
        pickerView.dataSource = self

        trailAnch = pickerView.trailingAnchor.constraint(equalTo: textField.trailingAnchor)
        widthAnch = pickerView.widthAnchor.constraint(equalTo: textField.widthAnchor)
        constraints[dropdownName]!.append([trailAnch, widthAnch])

        let dropdownWrapper = UIView()
        dropdownWrapper.translatesAutoresizingMaskIntoConstraints = false

        dropdownWrapper.addSubview(label)
        dropdownWrapper.addSubview(textField)
        dropdownWrapper.addSubview(pickerView)

        wrappers[dropdownName] = dropdownWrapper
        elements[dropdownName] = [label, textField, pickerView]

        let commandID = bleService.getCommand(commandName: dropdownName)
        bleService.send(aText: "c\(commandID)r:#")
    }

    for (index, sliderName) in sliders.enumerated() {
        constraints[sliderName] = [[]]
        let descLabel = UILabel()
        descLabel.translatesAutoresizingMaskIntoConstraints = false
        descLabel.text = sliderName

        var leadAnch = descLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
        constraints[sliderName]!.append([leadAnch])

        let valueLabel = UILabel()
        valueLabel.translatesAutoresizingMaskIntoConstraints = false
        valueLabel.text = "0"
        valueLabel.backgroundColor = .white

        let widthAnch = valueLabel.widthAnchor.constraint(equalToConstant: view.frame.width/6)
        var trailAnch = valueLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
        constraints[sliderName]!.append([trailAnch, widthAnch])

        let slider = UISlider()
        slider.translatesAutoresizingMaskIntoConstraints = false
        slider.isContinuous = false
        slider.addTarget(self, action: #selector(sliderValueChanged), for: .valueChanged)

        leadAnch = slider.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
        trailAnch = slider.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
        let topAnch = slider.topAnchor.constraint(equalTo: descLabel.bottomAnchor, constant: 5)
        constraints[sliderName]!.append([trailAnch, leadAnch, topAnch])

        let sliderWrapper = UIView()
        sliderWrapper.translatesAutoresizingMaskIntoConstraints = false

        sliderWrapper.addSubview(descLabel)
        sliderWrapper.addSubview(valueLabel)
        sliderWrapper.addSubview(slider)

        wrappers[sliderName] = sliderWrapper
        elements[sliderName] = [descLabel, valueLabel, slider]

        let commandID = bleService.getCommand(commandName: sliderName)
        bleService.send(aText: "c\(commandID)r:#")
    }

    for (index, checkboxName) in checkboxes.enumerated() {
        constraints[checkboxName] = [[]]
        let cbLabel = UILabel()
        cbLabel.translatesAutoresizingMaskIntoConstraints = false
        cbLabel.text = checkboxName

        let leadAnch = cbLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
        constraints[checkboxName]!.append([leadAnch])

        let checkbox = UISwitch()
        checkbox.translatesAutoresizingMaskIntoConstraints = false
        checkbox.addTarget(self, action: #selector(checkboxClicked), for: .valueChanged)

        let trailAnch = checkbox.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
        constraints[checkboxName]!.append([trailAnch])

        let checkboxWrapper = UIView()
        checkboxWrapper.translatesAutoresizingMaskIntoConstraints = false

        checkboxWrapper.addSubview(cbLabel)
        checkboxWrapper.addSubview(checkbox)

        wrappers[checkboxName] = checkboxWrapper
        elements[checkboxName] = [cbLabel, checkbox]

        let commandID = bleService.getCommand(commandName: checkboxName)
        bleService.send(aText: "c\(commandID)r:#")
    }

    for (index, textInputName) in textInputs.enumerated() {
        constraints[textInputName] = [[]]
        let textLabel = UILabel()
        textLabel.translatesAutoresizingMaskIntoConstraints = false
        textLabel.text = textInputName

        var leadAnch = textLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
        constraints[textInputName]!.append([leadAnch])

        let inputField = UITextField()
        inputField.layer.cornerRadius = 5
        inputField.translatesAutoresizingMaskIntoConstraints = false
        inputField.placeholder = textInputs[index]
        inputField.backgroundColor = .white
        inputField.addTarget(self, action: #selector(textfieldChanged), for: .valueChanged)

        let topAnch = inputField.topAnchor.constraint(equalTo: textLabel.bottomAnchor, constant: 5)
        let widthAnch = inputField.widthAnchor.constraint(equalToConstant: view.safeAreaLayoutGuide.layoutFrame.width/1.1)
        leadAnch = inputField.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
        constraints[textInputName]!.append([topAnch, widthAnch, leadAnch])

        let inputWrapper = UIView()
        inputWrapper.translatesAutoresizingMaskIntoConstraints = false

        inputWrapper.addSubview(textLabel)
        inputWrapper.addSubview(inputField)

        wrappers[textInputName] = inputWrapper
        elements[textInputName] = [textLabel, inputField]

        let commandID = bleService.getCommand(commandName: textInputName)
        bleService.send(aText: "c\(commandID)r:#")
    }

    for(index, phase) in timePickers.enumerated() {
        constraints[phase] = [[]]
        let descLabel = UILabel()
        descLabel.translatesAutoresizingMaskIntoConstraints = false
        descLabel.text = "Zeitschaltung \(index+1)"

        var leadAnch = descLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
        constraints[phase]!.append([leadAnch])

        let enabledSwitch = UISwitch()
        enabledSwitch.translatesAutoresizingMaskIntoConstraints = false
        enabledSwitch.addTarget(self, action: #selector(changeTimerState), for: .valueChanged)

        var topAnch = enabledSwitch.topAnchor.constraint(equalTo: descLabel.topAnchor)
        var trailAnch = enabledSwitch.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
        constraints[phase]!.append([trailAnch, topAnch])

        let showPickerButton = UIButton()
        showPickerButton.translatesAutoresizingMaskIntoConstraints = false
        showPickerButton.setTitle("Zeit auswählen", for: .normal)
        showPickerButton.backgroundColor = .darkGray
        showPickerButton.layer.cornerRadius = 5
        showPickerButton.addTarget(self, action: #selector(showTimePicker), for: .touchUpInside)

        topAnch = showPickerButton.topAnchor.constraint(equalTo: enabledSwitch.bottomAnchor, constant: 4)
        trailAnch = showPickerButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
        constraints[phase]!.append([topAnch, trailAnch])

        let timePicker = UIDatePicker()
        timePicker.backgroundColor = .white
        timePicker.isHidden = true
        timePicker.translatesAutoresizingMaskIntoConstraints = false
        timePicker.datePickerMode = .time
        timePicker.addTarget(self, action: #selector(changeTimer), for: .valueChanged)

        topAnch = timePicker.bottomAnchor.constraint(equalTo: enabledSwitch.bottomAnchor)
        trailAnch = timePicker.trailingAnchor.constraint(equalTo: enabledSwitch.trailingAnchor)
        constraints[phase]!.append([topAnch, trailAnch])

        //Brightness Slider Value Label

        let sliderValLabel = UILabel()
        sliderValLabel.translatesAutoresizingMaskIntoConstraints = false
        sliderValLabel.text = "0"
        sliderValLabel.backgroundColor = .white

        trailAnch = sliderValLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
        topAnch = sliderValLabel.topAnchor.constraint(equalTo: showPickerButton.bottomAnchor, constant: 10)
        var widthAnch = sliderValLabel.widthAnchor.constraint(equalToConstant: view.frame.width / 6)
        constraints[phase]!.append([trailAnch, topAnch, widthAnch])

        //Brightness Slider

        let valueSlider = UISlider()
        valueSlider.isContinuous = false
        valueSlider.translatesAutoresizingMaskIntoConstraints = false

        topAnch = valueSlider.topAnchor.constraint(equalTo: sliderValLabel.bottomAnchor, constant: 10)
        leadAnch = valueSlider.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
        trailAnch = valueSlider.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
        constraints[phase]!.append([topAnch, leadAnch, trailAnch])

        let timePickerWrapper = UIView()
        //timePickerWrapper.translatesAutoresizingMaskIntoConstraints = false

        timePickerWrapper.addSubview(descLabel)
        timePickerWrapper.addSubview(enabledSwitch)
        timePickerWrapper.addSubview(showPickerButton)
        timePickerWrapper.addSubview(timePicker)
        timePickerWrapper.addSubview(valueSlider)
        timePickerWrapper.addSubview(sliderValLabel)

        wrappers[phase] = timePickerWrapper
        elements[phase] = [descLabel, showPickerButton, enabledSwitch, timePicker, sliderValLabel, valueSlider]

        let commandID = bleService.getCommand(commandName: phase)
        bleService.send(aText: "c\(commandID)r:#")
    }

    for buttonName in buttons {
        constraints[buttonName] = [[]]
        let button = UIButton()
        button.translatesAutoresizingMaskIntoConstraints = false

        let widthAnch = button.widthAnchor.constraint(equalToConstant: contentView.frame.width/1.1)
        let xAnch = button.centerXAnchor.constraint(equalTo: view.centerXAnchor)
        constraints[buttonName]!.append([widthAnch, xAnch])

        let buttonWrapper = UIView()
        buttonWrapper.translatesAutoresizingMaskIntoConstraints = false

        wrappers[buttonName] = buttonWrapper
        elements[buttonName] = [button]
    }
}        

func changeContent(criteria: [String]) {
        for item in criteria {
            if(!contentView.contains(wrappers[item]!)) {
                contentView.addArrangedSubview(wrappers[item]!)
                for singleView in constraints[item]! {
                    for singleViewConstraint in singleView {
                        singleViewConstraint.isActive = true
                    }
                }
            }
        }
    }

func removeContent() {
    var criteria = [String]()
    switch(previousSetupMode) {
    case "d":
        criteria = d_kind_criteria
        break
    case "l":
        criteria = l_kind_criteria
        break
    case "m":
        criteria = m_kind_criteria
        break
    case "t":
        criteria = t_kind_criteria
        break
    default:
        break
    }
    for item in criteria {
        wrappers[item]!.removeFromSuperview()
    }
}

func changeView() {
    if(previousSetupMode != activeSetupMode) {
        removeContent()
    }
    switch(activeSetupMode) {
    case "d":
        changeContent(criteria: d_kind_criteria)
        break
    case "l":
        changeContent(criteria: l_kind_criteria)
        break
    case "t":
        changeContent(criteria: t_kind_criteria)
        break
    case "m":
        changeContent(criteria: m_kind_criteria)
        break
    default:
        break
    }
}

1 Ответ

3 голосов
/ 08 октября 2019

Проблема в том, что вы не предоставляете представлениям-оболочкам какую-либо высоту, поэтому элементы управления размещаются за пределами родительских представлений.

Вы можете подтвердить это двумя способами ...

1) В блоке

for (index, sliderName) in sliders.enumerated() {

добавьте:

sliderWrapper.backgroundColor = .green

(после создания представления sliderWrapper, конечно). Когда вы запустите приложение, вы не увидите зеленый фон, потому что высота sliderWrapper равна нулю:

enter image description here

2) И /или добавьте:

sliderWrapper.clipsToBounds = true

, и вы вообще не увидите элементы управления:

enter image description here

Чтобы решить эту проблему, вы можетедобавить ограничения:

        let sliderWrapper = UIView()
        sliderWrapper.translatesAutoresizingMaskIntoConstraints = false
        sliderWrapper.backgroundColor = .green

        sliderWrapper.addSubview(descLabel)
        sliderWrapper.addSubview(valueLabel)
        sliderWrapper.addSubview(slider)

        // add a topAnchor constraint from the top of descLabel to the top of sliderWrapper
        // center valueLabel vertically to descLabel
        // and a bottomAnchor from the bottom of slider to the bottom of sliderWrapper (negative if you want "padding")
        NSLayoutConstraint.activate([
            descLabel.topAnchor.constraint(equalTo: sliderWrapper.topAnchor, constant: 8.0),
            valueLabel.centerYAnchor.constraint(equalTo: descLabel.centerYAnchor),
            slider.bottomAnchor.constraint(equalTo: sliderWrapper.bottomAnchor, constant: -8.0),
        ])

Теперь фон видим ... элементы управления видимы ... и элементы управления можно взаимодействовать с:

enter image description here

...