1

I have tried some solutions from other posts but I haven't been able to solve the question I am going to post.

I have a custom UIView that draws a SoundWave and it contains a UIScrollView. The UIScrollView contains a main UIView that contains two other custom UIViews (marker left and right) and UIImage view where i render the sound wave.

private func initialize(){

    print("\(logClassName) initialize in Frame: \(frame)")

    //ScrollView
    addSubview(scrollView)
    scrollView.constraintToSuperViewEdges()


    //WaveView
    scrollView.addSubview(waveView)
    waveView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 0).isActive = true
    waveView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 0).isActive = true
    waveView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: 0).isActive = true
    waveView.heightAnchor.constraint(equalTo: scrollView.heightAnchor).isActive = true

    waveViewWidthConstraint = waveView.widthAnchor.constraint(equalToConstant: 1000)
    NSLayoutConstraint.activate([waveViewWidthConstraint])
    scrollView.contentSize = CGSize(width: waveViewWidthConstraint.constant, height: 0)

    //SoundWaveImageView
    waveView.addSubview(soundWaveImageView)
    soundWaveImageView.constraintToSuperViewEdges()


    //WaveView markers
    //TimeMarker
    waveView.addSubview(timeMarkerView)
    timeMarkerView.topAnchor.constraint(equalTo: waveView.topAnchor).isActive = true
    timeMarkerView.bottomAnchor.constraint(equalTo: waveView.bottomAnchor).isActive = true
    timeMarkerView.widthAnchor.constraint(equalToConstant: 1).isActive = true
    timerMarkerViewLeadingContraint = timeMarkerView.leadingAnchor.constraint(equalTo: waveView.leadingAnchor, constant: 20)
    NSLayoutConstraint.activate([timerMarkerViewLeadingContraint])


    //LeftMarker
    waveView.addSubview(leftMarkerView)
    leftMarkerView.topAnchor.constraint(equalTo: waveView.topAnchor).isActive = true
    leftMarkerView.bottomAnchor.constraint(equalTo: waveView.bottomAnchor).isActive = true
    leftMarkerView.widthAnchor.constraint(equalToConstant: leftMarkerView.triangleWidth).isActive = true
    leftMarkerViewLeadingConstraint = leftMarkerView.leadingAnchor.constraint(equalTo: waveView.leadingAnchor, constant: 0)
    NSLayoutConstraint.activate([leftMarkerViewLeadingConstraint])


    //RightMarker
    waveView.addSubview(rightMarkerView)
    rightMarkerView.topAnchor.constraint(equalTo: waveView.topAnchor).isActive = true
    rightMarkerView.bottomAnchor.constraint(equalTo: waveView.bottomAnchor).isActive = true
    rightMarkerView.widthAnchor.constraint(equalToConstant: rightMarkerView.triangleWidth).isActive = true
    rightMarkerViewLeadingConstraint = rightMarkerView.trailingAnchor.constraint(equalTo: waveView.leadingAnchor, constant: 50)
    NSLayoutConstraint.activate([rightMarkerViewLeadingConstraint])

}

As seen, the main UIView is constraint to the edges of the UIScrollView and the width constraint is the total length which changes according the needs. The function responsible for that is the following:

private func updateWidthConstraintValue(_ newWidth:CGFloat){

    waveViewWidthConstraint.constant = newWidth
    scrollView.contentSize = CGSize(width: waveViewWidthConstraint.constant, height: 0)

}

When implementing the zoom for the UIScrollView it works but it zooms both vertically and horizontally (as expected) but I would like to implement only horizontally. I have this so far:

func scrollViewDidZoom(_ scrollView: UIScrollView) {

    print("\(logClassName) TEST -> scrollView did zoom \(scrollView.zoomScale)")
    expandHorizontally(withScale: scrollView.zoomScale)
    //scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x, y: 0)
    //scrollView.contentSize = CGSize(width: scrollView.contentSize.width, height: scrollView.frame.height)
}

func viewForZooming(in scrollView: UIScrollView) -> UIView? {

    return waveView

}

private func expandHorizontally(withScale scale:CGFloat){

    let scaledWidth = currentWaveViewWidth * scale
    print("\(logClassName) TEST -> scaledWidth = \(scaledWidth)")

    updateWidthConstraintValue(scaledWidth)
    // Adjust content size and content offset
    scrollView.contentSize = CGSize(width: waveViewWidthConstraint.constant,
                                    height: 0);
    scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x,
                                       y: scrollView.contentOffset.y);

}

The UI Variables are declared like that:

lazy private (set) var scrollView:UIScrollView = {

    let rtView = UIScrollView()

    rtView.translatesAutoresizingMaskIntoConstraints = false
    rtView.backgroundColor = .clear

    rtView.bounces = false
    rtView.bouncesZoom = false

    rtView.minimumZoomScale = 1
    rtView.maximumZoomScale = 3

    rtView.delegate = self

    return rtView

}()

private (set) lazy var waveView:UIView = {

    let rtView = UIView()

    rtView.translatesAutoresizingMaskIntoConstraints = false
    rtView.backgroundColor = .clear
    rtView.addGestureRecognizer(UITapGestureRecognizer(target: self,
                                                       action: #selector(waveViewDidTouch)))

    return rtView

}()
private (set) var waveViewWidthConstraint = NSLayoutConstraint()

private (set) lazy var soundWaveImageView:UIImageView = {

    let rtView = UIImageView()

    rtView.translatesAutoresizingMaskIntoConstraints = false

    return rtView

}()

private (set) lazy var timeMarkerView:UIView = {
    let rtView = UIView()

    rtView.translatesAutoresizingMaskIntoConstraints = false
    rtView.backgroundColor = UIColor.AppColors.defaultTint

    return rtView
}()
private var timerMarkerViewLeadingContraint = NSLayoutConstraint()

private (set) lazy var leftMarkerView:MarkerView = {

    let rtView = MarkerView(title: AppHelper.printLocalized(withKey: "messages.start", targetSpecific: false), markerDirection: .right)

    rtView.translatesAutoresizingMaskIntoConstraints = false
    rtView.delegate = self

    return rtView

}()
private var leftMarkerViewLeadingConstraint = NSLayoutConstraint()

private (set) lazy var rightMarkerView:MarkerView = {

    let rtView = MarkerView(title: AppHelper.printLocalized(withKey: "messages.end", targetSpecific: false), markerDirection: .left)

    rtView.translatesAutoresizingMaskIntoConstraints = false
    rtView.delegate = self

    return rtView

}()
private var rightMarkerViewLeadingConstraint = NSLayoutConstraint()

I have tried to put all the relevant information as it is a big class.

Reimond Hill
  • 4,278
  • 40
  • 52
  • I did not check all your code but the usual way to prevent a scrollView to scroll vertically is to constraint its contents to the scrollView's parent view (top and bottom anchors constraints). So say you have parentView > scrollView > contentsView : scrollView should be constrained to parentView edges, and contentsView should be constrained to parentView top and bottom anchors. – Laurent Maquet Mar 29 '19 at 08:58
  • thanks for your reply. I see I constraint top only and I give equal height. Should remove the equal height and and constraint to bottom then? – Reimond Hill Mar 29 '19 at 09:01
  • I guess yes... Actually scrollView should be constrained to parentView edges, and contentsView should be constrained to scrollView edges AND to parentView top and bottom anchors. – Laurent Maquet Mar 29 '19 at 09:06
  • So, waveView (wrapper of Markers and UIImageView) constrained to edges of scrollview and constraint to top and bottom of scrollView's wrapper, right? So I do something on my delegate then? – Reimond Hill Mar 29 '19 at 09:18
  • Well my question was about zooming so it is not working... – Reimond Hill Mar 29 '19 at 09:23
  • it zooms as before but i can't scroll vertically and I get a constraint conflict when I constraint waveView to top and bottom of scrollView's parent view. – Reimond Hill Mar 29 '19 at 09:26
  • So you want to be able to scroll in both axis, but you want the zoom to be horizontal only, right ? – Laurent Maquet Mar 29 '19 at 09:28
  • yes. Ideally only zoom horizontally so height doesn't changes so there is no need to scroll vertically. does it make sense?¿ – Reimond Hill Mar 29 '19 at 09:32
  • Maybe try this solution: https://stackoverflow.com/a/35303735/3890944 from @Anna Dickinson – Laurent Maquet Mar 29 '19 at 09:36
  • could you provide whole code? maybe using some cloud or github? – Anton Novoselov Mar 29 '19 at 10:17
  • @ReimondHill - It sounds like you don't really want to **zoom**, rather, you want to **stretch / compress**? If I understand correctly what you're going for, you might want to search for `draw zoomable sound wave` ... I came across this - https://github.com/fulldecent/FDWaveformView - which, based on the gifs shown there, looks like it might be doing just what you're asking. If it won't work directly, you could probably dig through it to get an idea of how it's being done. – DonMag Mar 29 '19 at 13:00
  • @LaurentMaquet I think the post really helped me to find a temporary solution to my problem. – Reimond Hill Mar 29 '19 at 14:26

1 Answers1

0

I have figure out a temporary solution thank you to LaurentMaquet. At some point I will post the final soundWaveView as it is in development at the moment.

The changes made are:

1- Create a custom class for soundWave.

class WaveView:UIView{
    var unzoomedViewHeight: CGFloat = 0

    override var transform: CGAffineTransform{
        get{ return super.transform }
        set{
            var t = newValue
            t.d = 1.0
            t.ty = (1.0 - t.a) * unzoomedViewHeight/2
            super.transform = t
        }
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        unzoomedViewHeight = frame.size.height
        print("\(logClassName) did layout to \(AppHelper.traitStatus) -> frame = \(frame)")

    }

}

2- Make my soundView var be a SoundView

private (set) lazy var waveView:WaveView = {

    let rtView = WaveView()

    rtView.translatesAutoresizingMaskIntoConstraints = false
    rtView.backgroundColor = .clear
    rtView.addGestureRecognizer(UITapGestureRecognizer(target: self,
                                                       action: #selector(waveViewDidTouch)))

    return rtView

}()

Result

enter image description here

enter image description here

Reimond Hill
  • 4,278
  • 40
  • 52