بسم الله الرحمن الرحیم

در دو جلسه پیش دو آموزش در مورد ساخت کاستوم ویو ها خدمتتون تقدیم شد که امیدوارم اول اونا رو بخونید از اینجا میتونید دو آموزش قبلی رو پیدا کنید (آموزش قسمت اول و آموزش قسمت دوم) در این جلسه قصد داریم یه طرحی که توی سایت دیریبل دیدم باهم پیاده کنیم

خب بهتره قبل از شروع طرحمون رو یه براندازی بکنیم

خب یه نقشه راه بهتون بدم:

۱. باید از دو آموزش قبل استفاده کنیم یعنی ما در نهایت فقط یه ویو میخوایم که توی اون ویو همه اینا باشه یعنی وقتی ویو رو توی اینترفیس بیلدر اضافه کردیم دقیقا طرح بالا رو ببینیم و وقتی روی پلی کلیک کردیم شروع به پخش شدن کنه (حداقل ویو باشه و یه دلیگت رو اجرا کنه)

۲. اگر به تصویر بالا دقت کنید یه ویویی هست که یه سری خطوط داره که بعضی وقتا یه قسمت هاییش دو رنگ هست (پس یه ویوی دیگه ای باید توی این ویو ها بسازیم)

۳. ویو هایی مثل دکمه پخش و ارسال و دلیت رو باید از اون راهی که توی آموزش قسمت اول داده شد بسازیم و اون ویویی که قراره صدا رو نشون بده با آموزش قسمت دوم

 

در ابتدا پروژه ای به اسم VoiceMessageView بسازید و توی پروژه یه فولدر (New Group) به اسم Source بسازید

۱. توی این فولدر یه فایل View بسازید (با فرمت xib) و اسمش رو VoiceMessageView قرار بدید

۲. یه فایل سویفت هم به همین اسم بسازید توی همون فولدر و یادتون باشه که از UIView ارث بری کرده باشه

خب توی جلسات گذشته در مورد File's Owner صحبت کردیم و میدونیم که باید File's Owner رو بهش بدیم 

چیزی که باید بهش بدیم همون فایل سویفتی هست که چند لحظه پیش ساختیم پس روی File's Owner کلیک کنید و توی تب Attributes Inspector اسم فایل سویفت رو بهش بدید

خب میتونیم اینجا کار های ساده ای که بلدیم رو انجام بدیم مثلا یه UIView بکشید وسط صفحه و به اطراف وصلش کنید و توی runtime attribute یک attribute جدید بسازید به اسم و مقادیر زیر:

layer.cornerRadius = 10

یادتون باشه که پس زمینه کل ویو رو clear color بزارید که بی رنگ باشه

خب حالا باید یه uiView بکشیم توی صفحه و یه سایز ثابت بهش بدیم و رنگشم آبی کنیم (کد : #1D74F5) 

به وسط صفحه بیارید و از چپ بهش فاصله بدید تا به شکل زیر در بیاد

خب الان باید بریم سراغ یه ویویی که باید همشو با کد بنویسیم یعنی اون ویوی اصلی که با فرکانس صدا کم و زیاد میشه

یه فایل جدید سویفت به اسم WaveSoundView.swift  بسازید و از UIView ارث بری کنید و تابع draw رو override کنید

خب برای کشیدن یه خط عمودی همونجوری که توی آموزش دوم به شیوه کشیدن یه خط پرداختیم عمل میکنیم

 

            let linePath = UIBezierPath()
            linePath.lineCapStyle = .round
            linePath.lineWidth = 8
            linePath.move(to: CGPoint(x: 0, y: 0))
            linePath.addLine(to: CGPoint(x: 0, y: rect.height))
            UIColor.blue.setStroke()
            linePath.stroke()

خب توضیح کدهای بالا بسیار ساده است ابتدا یه UIBezierPath میسازیم و توی خط دوم میخوایم وقتی خط کشیده میشه اول و آخرش گرد باشه و مربعی نباشه خط بعدیشم که عرض خطی که قراره بکشیم رو دادیم  و اشاره گر رو بردیم روی نقطه صفر و صفر (یعنی سمت چپ بالا) و یه خط از اونجا تا پایین کشیدم و بعد رنگ بهش دادیم و با خط آخر رسم کردیم.

حالا قبل از اینکه برید به فایل main.storyboard بهتره اول کلمه کلیدی @IBDesignable رو بالای اسم کلاستون بنویسید تا قابل رسم و نشون دادن باشه بعد وارد main.storyboard  بشید (وارد این فایل میشیم که فقط بتونیم ببینیم داریم چه کار میکنیم و کدی که زدیم چه طرحی رو روی صفحه رسم میکنه مگرنه بعدش باید همه رو به فایل VoiceMessageView منتقل کنیم) خب یه uiview بکشید توی صفحه و کلاسشو همون WaveSoundView بدید تا ایکس کد شکلشو براتون رسم کنه

خب ما با نوشتن کد بالا توی تابع draw فقط میتونیم یه خط بکشیم یعنی تا اینجای کار فقط طرح زیر رو ساختیم

 

اما توی طبق طرح میدونیم که باید چندین خط متوالی با فاصله رسم کنیم فکر کنم یه حلقه نیاز داشته باشیم یه حلقه مثل حلقه زیر رو بنویسید:

   for i in 0 ... 10 {

ولی وقتی اجرا میکنید میبینید هیچ تاثیری نداشته (چرا؟) چون همه خط ها رو از یه نقطه رسم کردیم پس همه ۱۰ خط روی هم افتادن پس بهتره دو تا متغییر به اسم های lineWidth و marginLeft بسازیم تا بتونیم با محاسبات ریاضی فاصله بین هرکدوم و محل رسم هر خط رو حساب کنیم و توی نقطه مربوطه رسم کنیم.

    let lineWidth = 3
    let marginLeft = 3

خب حالا اون کدهای اولیه ای که توی حلقه بردید رو یه نگاهش بندازید  و x و y شون رو محاسبه کنید

اگه رفتید سراغشون سود کردید چون الان که توضیح میدم حکایت ضرب المثل معما چو حل گردد آسان شود میشه و میگید چیزی نداره اما بهتون میگم بهتره خودتون محاسبه کنید خیلی هم سادست

۱. مقدار y ها دست نمیخوره و همون قبلی میمونه و این x هاست که باید تغییرشون بدیم پس یه متغییر توی اون for بسازید به اسم currentX و مکان x این خط جدید رو محاسبه کنید به شکل زیر

let currentX : CGFloat = CGFloat(Float(i) * (marginLeft + lineWidth) + marginLeft)

CGFloat رو لازم داریم چون وقتی میخوایم از این متغییر استفاده کنیم برای دادن نقطه باید حتما از نوع CGFloat باشه

ممکنه محاسبه کردن شما با من فرق داشته باشه مثلا اون marginLeft آخری رو که من باهاش جمع زدم نداشته باشید ولی دقت کنید اگه اون نباشه و i صفر باشه مقدار متغییرتون صفر میشه و اولین خطتتون از روی ۰ و ۰ رسم میشه و اگر دقت کنید اون دایره بودن خط پیدا نمیشه پس باید فاصله داشته باشه.

خب  از اولین کدی که خدمتتون دادم خط ۴ و ۵ رو نگاه کنید. اون باید به کد زیر تغییر کنه

 linePath.move(to: CGPoint(x: currentX, y: 0))
 linePath.addLine(to: CGPoint(x: currentX, y:  rect.height))

حالا وقتی اجرا کنید میبینید خطوط با فاصله از هم رسم شدن

شاید بعد از اجرا متوجه این بشید که خطوطتون مثل تصویری که دیدید گرد نیست و مربعی شکله برای رفع این مشکل بهتره یه متغییر به اسم marginTopDown بسازید و بهش یه مقدار مثلا ۵ بدید (متغییر رو بالای تابع draw تعریف و مقدار دهی کنید)

خب بهتره یه متغییر هم توی خود تابع draw بسازیم به اسم height و width که این دوتا متغییر طول و عرض ویوی ما رو نگه دارن یعنی همون rect.height رو بریزیم

اما از حالا برای راحتی کار از height همون marginTopDown رو کم کنیم

        let height = rect.height - marginTopDown
        let width = rect.width

خب حالا میتونیم خط ۴ و ۵ توی کد اول رو هم تغییر بدیم باز و مقدار y ها رو اینجوری بزاریم

 linePath.move(to: CGPoint(x: currentX, y: spaceTopDown))
 linePath.addLine(to: CGPoint(x: currentX, y:  height))

حالا اگه برنامه رو اجرا کنید شاید شگفت زده بشید چون تقریبا چیزی که میخواستید رو تونستید رسم کنید

خب تا اینجا خیلی خوب پیش رفتیم الان باید یه کاری کنیم که همشون یه اندازه نباشند در حقیقت باید طبق فایل صوتی یا صدای ظبط شده بتونیم اینا رو کم و زیاد کنیم ولی ما نمیخوایم این کارو انجام بدیم بلکه میخوایم خود برنامه نویس با استفاده از یه تابعی که ما مینویسیم بهش درصد بده و ما هم خط رو رسم کنیم اینجوری پیدا کردن فرکانس صدا به عهده خود برنامه نویس میره و ما فقط این خط ها رو رسم میکنیم

گفتم درصد شاید داستان رو متوجه شده باشید

من قصد دارم یه ارایه بسازم و توش درصد هرکدوم از خطوط رو نگه دارم و تعداد خط ها هم به اندازه تعداد عناصر ارایه باشه

اوه راستی اگه یه وقت تعداد اعضای ارایه انقدر زیاد شد که دیگه وقتی رسم کنیم توی ویو پیدا نباشه چی؟ (فکر اونجاشم کردم :))

خب اول یه ارایه قبل از تابع draw بسازید.

var arrayOfBounds : [CGFloat] = [100]

این یه ارایه از CGFloat هست که فعلا فقط یه عدد ۱۰۰ توشه و ۱۰۰ هم یعنی صد در صد یعنی یه خط از بالا تا پایین

برای محاسبه درصد باید چیکار کنیم؟

توی همون حلقه یه متغییر دیگه به اسم disFromTopDown به معنای فاصله از بالا  و پایین بسازید و بدون اینکه به پایین نگاه کنید اول خودتون کدشو بنویسید

اینم کدش:

let disFromTopDown = (height - (height * arrayOfBounds[i]/100))/2

خب اگه لازمه توضیح بدم؟

ببینید ما میخوایم ببینیم باید چقدر از بالا و پایین فاصله داشته باشه شما فرض کنید میخوایم ارتفاعش ۸۰ درصد باشه پس باید ۲۰ درصد از بالا و پایینش فاصله داشته باشه اما اگه ۲۰ درصد از بالا و ۲۰ درصد هم از پایین فاصله بگیره که میشه ۴۰ درصد یعنی ارتفاع اون خط ۶۰ درصد میشه پس باید تقسیم بر ۲ کنیم که هم از بالا فاصله بدیم هم از پایین

بعد از ساخت این متغییر شاید متوجه این شده باشید که اگر اون i درون کد بالا قراره زیاد بشه و ما هم الان ۱ عنصر توش داریم برنامه کرش میکنه. درسته حق با شماست پس بهتره تعداد عناصرشو ۱۱ تا کنید یعنی کد قبلی رو که اضافه کردید اینجوری تغییرش بدید

var arrayOfBounds : [CGFloat] = [100,90,80,70,70,70,50,60,50,50,20]

خب حالا بهتر شد

مثل دفعات قبل باید بریم به خط ۴ و ۵ همون کد اول یعنی توابع move و addLine تا مقدار y هاشونو برای آخرین دفعه تغییر بدیم

            linePath.move(to: CGPoint(x: currentX, y: disFromTopDown + marginTopDown))
            linePath.addLine(to: CGPoint(x: currentX, y: height-disFromTopDown))

خب حالا کافیه برنامه رو اجرا کنید و از این شاهکارتون لذت ببرید

خب ما که نمیتونیم همیشه از این ارایه استفاده کنیم یعنی بالاخره لازم میشه یکی بتونه از بیرون بهش یه مقدار جدیدی اضافه کنه پس باید یه تابع بنویسیم که برنامه نویسی که قراره از لایبراری ما استفاده کنه اون تابع رو صدا بزنه و بهش یه مقدار جدیدی اضافه کنه و بعد از اضافه کردن ما از اول همه رو رسم کنیم

اما یه سوال پیش میاد حالا اگه برنامه نویس بیش از حد اضافه کرد چی یعنی تا ۱۰۰ تاش مثلا توی ویو جا میشه ولی برنامه نویس ۲۰۰ تا اضافه کنه چه اتفاقی میفته

هیچ اتفاق خاصی نمیفته و بعد از ۱۰۰ تا دیگه هیچ تغییری پیدا نمیکنه پس اول تابع رو بنویسیم بعد برا اونم یه فکری میکنیم

تابه زیر رو به کلاستون اضافه کنید

 func addBar(percent : CGFloat, at : Int = -1)
    {
        if (at == -1){
            arrayOfBounds.append(percent)
        }
        else {
              arrayOfBounds.insert(percent, at: at)
        }
        setNeedsDisplay()
    }

خب ما با صدا کردن این تابع از بیرون میتونیم یه مقدار جدید به ارایه اضافه کنیم و رسم کنیم پس بهتره حلقه فور رو از ۰ تا تعداد عناصر ارایه بچرخه نه از ۰ تا ۱۰:

  for i in 0 ... arrayOfBounds.count-1 {

خب حالا اگه دوست داشتید ران بگیرید ولی هیچ چیزی تغییر نمیکنه چون شما اصلا این تابع رو از جایی صدا نکردید حالا فرض رو بر این بزاریم که درست عمل میکنه ولی خب مشکل اونجاست که ما میخوایم اگه برنامه نویس بیش از حد به ارایه اضافه کرد ما بریم از آخرش ارایه بخونیم یعنی اول تعداد خطوطی که میتونیم توی طول ویو رسم کنیم رو بدست بیاریم بعد ببینیم اگه تعداد عناصر آرایه از تعداد خطوط قابل رسم در طول ویو بیشتر بود تفاوت عددیش رو بدست بیاریم و ارایه رو از آخر بخونیم

متوجه شدید؟ مثلا فرض کنید ما محاسبه کردیم که میتونیم ۲۰ تا خط توی ویو نشون بدیم بعد ما اگه تنها ۲۰ عنصر اول ارایه رو بخونیم دیگه رسم عنصر ۲۱ ام فایده نداره ولی ما ترجیح میدیم که عنصر ۱ رو رسم نکنیم در عوض از عنصر ۲ تا ۲۱ رو رسم کنیم پس باید تفاوت عددی <<تعداد خطوط قابل رسم با تعداد عناصر آرایه>> رو بدست بیاریم و این اختلاف رو توی کدمون به کار ببریم تا از آخر ارایه بتونیم استفاده کنیم و رسم کنیم

خب یه متغییر به اسم total توی تابع draw قبل از for بنویسید

  let total : CGFloat = width / CGFloat(marginLeft + lineWidth)

حالا میتونید اختلاف این توتال رو با تعداد عناصر آرایه حساب کنید تا ببینید ارایه چقدر از خطوط قابل رسم شما بیشتره

var diff = arrayOfBounds.count - Int(total)

خب حالا برای اینکه از آخر ارایه شروع کنید یعنی مثلا اگر ۲۰ تا خط قابل رسم هست شما میخواید ۲۰ تای آخر رو رسم کنید نه ۲۰ تای اول رو پس باید متغییر disFromTopDown رو به کد زیر تغییر بدید

let disFromTopDown = (height - (height * arrayOfBounds[diff + i]/100))/2

اما یه سوال حالا اگه اومدیم و تعداد قابل رسم ما از تعداد ارایه بیشتر بود یعنی مثلا هنوز برنامه نویس انقدر به ارایه اضافه نکرده که ارایه تعدادش از قابل رسم بیشتر بشه چی؟ اصلا یه سوال دیگه به نظرتون الان حلقه for داره درست عمل میکنه یعنی به نظرتون بهتر نیست به اندازه total بچرخه؟

پس یه سوالی که پیش میاد اینه که ممکنه گاهی تعداد عناصر ارایه از تعداد خطوط قابل رسم ما کمتر باشه و برنامه کرش میکنه چون اون متغییر  diff یه عدد منفی میشه و کار رو خراب میکنه پس بهتره دوتا تغییر توی کد هامون بدیم یکی اینکه چک کنیم اگه diff منفی باشه مقدارشو برابر صفر در نظر بگیره:

 if diff < 0 {diff = 0}

کد بالا رو دقیقا زیر متغییر total بریزید یعنی قبل از for 

حلقه فور هم باید درست تر بشه یعنی به چک کنیم ببینیم کی کمتر total یا arrayOfBounds.count بعد بگیم از صفر تا کمتره بچرخه

پس

 for i in 0 ... min(Int(total),arrayOfBounds.count)-1 {

حالا همه چیز درست شد

وارد فایل main.storyboard  بشید و از اونجا توی کلاس ViewController.swift تون یه OUTLET بسازید و توی viewDidLoad یه حلقه بزارید و هرچقدر دوست دارید با استفاده از اون تابع addBar به اون ویو خط اضافه کنید

و برنامه رو اجرا کنید

میبینید که همه چی درست عمل میکنه

حالا حتی میتونید با استفاده از کد زیر با فاصله زمانی اون تابع رو صدا کنید تا از ویوی خودتون بیشتر لذت ببرید

   override func viewDidLoad() {
        super.viewDidLoad()
       Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(timerFunc), userInfo: nil, repeats: true)
    }

    @objc func timerFunc () {
        let randomInt = Int.random(in: 20..<99)
        waveSoundView.addBar(percent: CGFloat(randomInt))
    }

تا اینجا تونستیم یه چنین چیزی بسازیم:

ولی پامون رو فراتر میزاریم و میخوایم یه چیزی مثل طرح دوم  و سوم پیاده کنیم یعنی میخوایم از یه جایی رنگ اون خطوط تغییر پیدا کنه و هرچی به سمت آخر میریم کمرنگ بشه

پس چند تا متغییر جدید میسازیم توی کلاسمون

    let barOpacityCount : CGFloat = 9
    var rightOpacity = true
    var leftOpacity = false
    var playingMode = true
    let played = 15
    let grayLightColor = UIColor(red: 213.0/255, green: 216.0/255, blue: 219.0/255, alpha: 1.0)

شاید یکم اسم هاشون گنگ باشه پس بهتره کار هر متغییر رو توضیح بدم

۱. تعداد خطوطی که قراره از سمت چپ یا راست کم کم رنگشون کمرنگ بشه یعنی اینجا قراره از ۹ تا سمت راست و ۹ تا سمت چپ شروع کنه به کمرنگ شدن و با وسطش کاری نداره

۲. دومین متغییر یه بولین هست که میگه از سمت راست کمرنگ بشه یا نه؟

۳. از سمت چپ کمرنگ بشه یا نه؟

۴. اینو ساختم وقتی که بخوایم مثل طرح سومی مثلا یه صوتی رو پخش کردید و کلش اول خاکستری رنگه و هرچی میگذره خطوط آبی میشن و خطوط آبی به معنای اینه که تا اینجاش پخش شده

۵. این متغییر مشخص میکنه چند تا از خط های اول باید آبی رنگ باشن (این متغییر زمانی استفاده خواهد شد که متغییر بالایی برابر true باشه)

۶. رنگ خاکستری برای اون خطوط است

خب حالا بریم سراغ کد

باید قبل از رنگ کردن خط یعنی اون خط UIColor.blur.setStroke() خطوط زیر رو اضافه کنید

           var alpha : CGFloat = 1
            if ((leftOpacity && i < Int(barOpacityCount))) || (rightOpacity && i > Int(total - barOpacityCount)) {
                alpha = (i < Int(barOpacityCount) ? CGFloat(i) : (total - CGFloat(i)))  * (1/barOpacityCount)
            }
            var currentColor = UIColor.blue
            if playingMode && i > played {
                currentColor = grayLightColor
            }
            currentColor.withAlphaComponent(alpha).setStroke()

خب خیلی واضحه که توی ایف چک میکنم ببینم اگه leftOpacity برابر true هست و الان خطی که داریم رسم میکنیم کمتر از ( اون ۹ هست) یعنی جز ۹ تای اول هستیم کد زیر ایف اجرا میشه یا حتی اگه rightOpacity برابر true هست و خطی که داره رسم میشه جزو ۹ خط آخر هست باز خط زیر که مقدار alpha یا همون opacity رو حساب میکنه اجرا میشه

چجوری حساب میکنه؟ خیلی ساده است ببینید کافیه شما بگید اپاسیتی کل یعنی ۱ رو تقسیم بر تعداد خطوطی که میخواید opacity داشته باشن یعنی کمرنگ تر باشن میکنم و بعد در مکان کنونی ضرب میکنم مثلا فرض کنید که میخوایم ۹ تا خط اول oapcity داشته باشن یعنی یواش یواش کمرنگ بشن پس باید اول چک کنیم که خط ما جز ۹ خط اول هست اگر هست مکانش رو در یک نهم ضرب کنیم اینجوری هرچی به خط نهم نزدیک تر میشیم خطوطمون پر رنگ تر میشه

خط ۶ هم برای اینه که رنگ رو تغییر بدیم یعنی اگر حالت playing فعال بود چک میکنیم خطی که داریم رسم میکنیم جز پخش شده هاست یا هنوز پخش نشده اگر جز پخش نشده هاست رنگشو به خاکستری تغییر میدیم و در نهایت هم رنگ خط رو اونجوریش میکنیم که alpha توش تاثیر کنه

قبل از اینکه اجرا کنید بهتره خط زیر رو هم بالای متغییر diff  قرار بدید

       if (arrayOfBounds.count > Int(total)) {
            rightOpacity = true
        }

این کد چک میکنه که اگه تعداد ارایه ها از تعداد خطوط قابل رسم بیشتر شد به صورت اتوماتیک اپاسیتی سمت راست رو فعال میکنه که به کاربر القا کنه فقط این خطوط نیست و ادامه داره

خب دیگه کارمون تقریبا تمومه

میتونید این ویو رو قشنگ تر کنید یعنی متغییر های مورد نیاز رو توی حالت @IBInspectable قرار بدید که بشه از توی اینترفیس فیلدر تغییرشون داد و حتما برای یه سریشونم یه setter بنویسید که وقتی تغییر کردن از اول رسم بشه بهتره رنگ آبی رو هم متغییر بسازید و از اون استفاده کنید یعنی lineColor بسازید و به جای اینکه بنویسید UIColor.blue بنوبسید  lineColor که بعدا از توی اینترفیس بیلدر قابل تغییر باشه

حالا بعد از همه اینا میتونید همین ویو رو توی VoiceMessageView.xib بریزید و اونجا کنارش دو تا عکس حذف و ارسال هم بزارید و در نهایت کد VoiceMeesageView.swift رو هم بنویسید تا یه ویو کامل ساخته باشید

 

کد آخر فایل WaveSoundView.swift به شکل زیر شد:

//
//  WaveSoundView.swift
//  VoiceRecorderViewSorce
//
//  Created by Mohammad Fallah on 10/21/1397 AP.
//  Copyright © 1397 Mohammad Fallah. All rights reserved.
//

import UIKit
@IBDesignable
class WaveSoundView: UIView {
    let barOpacityCount : CGFloat = 9
    var rightOpacity = true
    var leftOpacity = false
    var playingMode = true
    let played = 15
    let grayLightColor = UIColor(red: 213.0/255, green: 216.0/255, blue: 219.0/255, alpha: 1.0)
    @IBInspectable var lineWidth : Float = 4 {
        didSet {
            setNeedsDisplay()
        }
    }
    @IBInspectable var lineColor :UIColor  = UIColor.blue {
        didSet {
              setNeedsDisplay()
        }
    }
    @IBInspectable var marginTopDown : CGFloat = 10 {
        didSet {
            setNeedsDisplay()
        }
    }
    @IBInspectable var marginLeft : Float = 10 {
        didSet {
            setNeedsDisplay()
        }
    }
    var arrayOfBounds : [CGFloat] = [100]

    override func draw(_ rect: CGRect) {
        let height = rect.height - marginTopDown
        let width = rect.width
        let total : CGFloat = width / CGFloat(marginLeft + lineWidth)
        if (arrayOfBounds.count > Int(total)) {
            rightOpacity = true
        }
        var diff = arrayOfBounds.count - Int(total)
        if diff < 0 {diff = 0}
        for i in 0 ... min(Int(total),arrayOfBounds.count)-1 {
            let currentX : CGFloat = CGFloat(Float(i) * (marginLeft + lineWidth) + marginLeft)
            let disFromTopDown = (height - (height * arrayOfBounds[diff + i]/100))/2
            let linePath = UIBezierPath()
            linePath.lineCapStyle = .round
            linePath.lineWidth = CGFloat(lineWidth)
            linePath.move(to: CGPoint(x: currentX, y: disFromTopDown + marginTopDown))
            linePath.addLine(to: CGPoint(x: currentX, y: height-disFromTopDown))
            var alpha : CGFloat = 1
            if ((leftOpacity && i < Int(barOpacityCount))) || (rightOpacity && i > Int(total - barOpacityCount)) {
                alpha = (i < Int(barOpacityCount) ? CGFloat(i) : (total - CGFloat(i)))  * (1/barOpacityCount)
            }
            var currentColor = lineColor
            if playingMode && i > played {
                currentColor = grayLightColor
            }
            currentColor.withAlphaComponent(alpha).setStroke()
            linePath.stroke()
        }
    }
    func addBar(percent : CGFloat, at : Int = -1)
    {
        if (at == -1){
            arrayOfBounds.append(percent)
        }
        else {
              arrayOfBounds.insert(percent, at: at)
        }
        setNeedsDisplay()
    }

}

میتونید توی گیتهاب هم کد این پروژه رو ببینید

پروژه رو به صورت کامل تر و قشنگ تر روی گیتهاب ببینید» اینجا کلیک کنید