GeRyCh
GeRyCh

Reputation: 1628

iOS Custom control with dynamic elements

I've started to build my first open source project I would like to share to a world. It's a simple Audio Player view which will work with AudioProvidable protocol and then will play an audio data after it's received.

For now I have an IBDesignable class inherits from UIView which builds itself by defining constraints for its subviews like this:

override init(frame: CGRect) {
        super.init(frame: frame)
#if !TARGET_INTERFACE
        translatesAutoresizingMaskIntoConstraints = false
#endif
        prepareView()
        updateUI()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        prepareView()
        updateUI()
    }

    func prepareView() {
            //Necessary in order to set our own constraints
            //btnPlay is already doing the same inside buttonWithImage(_:)
            spinner.translatesAutoresizingMaskIntoConstraints = false
            sliderAudioProgress.translatesAutoresizingMaskIntoConstraints = false

            //Add necessary subviews to start setting up layout
            addSubview(sliderAudioProgress)
            addSubview(lblAudioDuration)
            addSubview(lblAudioProgress)
            addSubview(spinner)
            addSubview(btnPlay)
            addSubview(lblTrackNumber)
            addSubview(btnNextTrack)
            addSubview(btnPreviousTrack)

            //Height of play & next/previous track buttons defines AudioPlayerView height
            let viewHeight:CGFloat = 48 * 2

            //Setup UI layout using constraints
            NSLayoutConstraint.activate([

                heightAnchor.constraint(equalToConstant: viewHeight),

                //Play button constraints
                btnPlay.topAnchor.constraint(equalTo: topAnchor),
                btnPlay.leadingAnchor.constraint(equalTo: leadingAnchor),

                //Spinner constraints
                spinner.centerYAnchor.constraint(equalTo: btnPlay.centerYAnchor),
                spinner.centerXAnchor.constraint(equalTo: btnPlay.centerXAnchor),

                //Progress label constraints
                lblAudioProgress.leadingAnchor.constraint(equalTo: btnPlay.trailingAnchor),
                lblAudioProgress.centerYAnchor.constraint(equalTo: btnPlay.centerYAnchor),

                //Duration label constraints
                lblAudioDuration.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -spacing),
                lblAudioDuration.centerYAnchor.constraint(equalTo: btnPlay.centerYAnchor),

                //Audio Progress Slider constraints
                sliderAudioProgress.leadingAnchor.constraint(equalTo: lblAudioProgress.trailingAnchor, constant: spacing),
                sliderAudioProgress.trailingAnchor.constraint(equalTo: lblAudioDuration.leadingAnchor, constant: -spacing),
                sliderAudioProgress.centerYAnchor.constraint(equalTo: btnPlay.centerYAnchor),

                lblTrackNumber.centerXAnchor.constraint(equalTo: centerXAnchor),

                btnPreviousTrack.topAnchor.constraint(equalTo: btnPlay.bottomAnchor),
                btnPreviousTrack.trailingAnchor.constraint(equalTo: lblTrackNumber.leadingAnchor, constant: -spacing),

                btnNextTrack.topAnchor.constraint(equalTo: btnPlay.bottomAnchor),
                btnNextTrack.leadingAnchor.constraint(equalTo: lblTrackNumber.trailingAnchor, constant: spacing),

                lblTrackNumber.centerYAnchor.constraint(equalTo: btnNextTrack.centerYAnchor)
            ])

            //Spinner setup:
            spinner.hidesWhenStopped = true

            //btnPlay setup:
            btnPlay.addTarget(self, action: #selector(AudioPlayerView.playButtonPressed), for: .touchUpInside)

            //Slider setup
            sliderAudioProgress.thumbTintColor = UIColor(keyFromAppColorPalette: "secondary_color")
            sliderAudioProgress.addTarget(self, action: #selector(AudioPlayerView.durationSliderValueChanged), for: .valueChanged)
            sliderAudioProgress.addTarget(self, action: #selector(AudioPlayerView.durationSliderReleased), for: .touchUpInside)
            sliderAudioProgress.isEnabled = false
            sliderAudioProgress.isContinuous = true
            sliderAudioProgress.semanticContentAttribute = .playback

            //View's UI effects to make it look better
            layer.cornerRadius = 6
            layer.masksToBounds = true
            layer.borderColor = UIColor.black.cgColor
            layer.borderWidth = 2
            semanticContentAttribute = .playback

            spinner.startAnimating()
            btnPlay.isHidden = true
        }

As a result that is what we have when audio is receiving:

enter image description here

After the audio received:

enter image description here

In case when there's only one audio to play the bottom part, switches between tracks is redundant. Where do I have to put a checking code for audio tracks count to redesign my UI? I don't want to call prepareView() in didSet {} of audio source array, because I don't want existing elements be added twice. Regards.

Upvotes: 0

Views: 171

Answers (1)

DonMag
DonMag

Reputation: 77700

You could add a bool flag to see if the code is running for the first time, or, what I find easier and maybe a little more logical...

    func prepareView() {

        //Necessary in order to set our own constraints
        //btnPlay is already doing the same inside buttonWithImage(_:)
        spinner.translatesAutoresizingMaskIntoConstraints = false
        sliderAudioProgress.translatesAutoresizingMaskIntoConstraints = false

        // if our subviews have not yet been added
        if sliderAudioProgress.superview == nil {

            //Add necessary subviews to start setting up layout
            addSubview(sliderAudioProgress)
            addSubview(lblAudioDuration)
            ... etc

        }

        // continue here with any "every time" setup tasks
    }

Upvotes: 1

Related Questions