Kodeco Forums

Video Tutorial: Beginning Core Graphics Part 1: Getting Started

In this core graphics video tutorial you'll get started drawing inside your views with paths.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/3402-beginning-core-graphics/lessons/2
2 Likes

Great tutorial. Very nice explanations. Look forward to more. Question on drawing partial borders - perhaps you will cover this in an upcoming video. I want to be able to draw a partial border on only some sides of a custom UICollectionViewCell inside a custom UICollectionViewLayout prepareLayout method. Is this possible? I think there is no drawRect override method for a custom layout class. So is the only option to override drawRect in the custom UICollectionViewCell? Thanks.

I’m a bit rusty on collection views. But I wouldn’t have thought you want to draw a border in prepareLayout(). That’s for laying out the cell, which is a different process from actually drawing it on screen. A border should only be drawn when the cell is ready to be drawn, which would be in drawRect(_:).

Great tutorial!

Is it possible to adjust the width of the line, for the challenge, via dragging, instead of clicking repeatedly to in/decrement? I’m used to Unity’s equivalent of IBInspectables, which allowed for both “digital” and “analog” control.

Thanks!

Not for attributes in Storyboard. You may have edited SpriteKit scenes in the Scene Editor, where you can drag to change values. Hopefully one day the Storyboard numeric attributes will have this functionality too.

1 Like

This is all great info. Cannot wait for the next part.

1 Like

Amazing tutorial, I can learn a lot by just looking at the started project, I don’t know before that I can split storyboard and use segue to refer to scene in another storyboard.

Amazing Caroline ! Love your tutorial !

1 Like

Hi Caroline ,
I learn CG from your tutorial and apply it to my project but I have a problem with setNeedDisplay() and draw(_ rect: CGRect) function . I posted my question at ios - call setNeedsDisplay() inside delegate not work (Swift 3.0) - Stack Overflow
because Raywenderlich editor is hard to post code so I post my question on stackoverflow Sorry for the inconvenience. It seems no one in stackoverflow can answer my question, So please help me if you have free time, thank you so so much

@thanhnguyen - Hi - yes - I haven’t yet replied to your other message about subtitles - Sorry! (Subtitles are not on older videos - I hope CALayers will be updated at some time, but I don’t know exactly when or if.)

Firstly code is really easy on the forum. You just use the backtick character ```.

For example, this is code is surrounded with the ``` and formats it into code.

Or you can indent by four spaces.

this is also code

It’s standard markdown, so words surrounded with * are italic like this and words surrounded with ** are bold like this

I hope that helps. (But you get a wider audience on stackoverflow, so it’s often better to ask there, if it’s not directly concerning an RW tutorial.)

In your code in stackoverflow, you are calling setNeedsDisplay() from init() and I wonder why. You should hardly ever need to call setNeedsDisplay() unless you are actually doing some CG drawing.

(And the correct spelling is delegate)

Could you explain further what your intentions are?

Hi Caroline , thank for quickly reply

1)about setNeedsDisplay() from init() please ignore it, this is my stupid mistake, I’m sorry.I removed it

  1. “Could you explain further what your intentions are?” in WaveFormView when I finish draw I want to say ControllerWaveForm hey I finish draw what should I do next ( I set a callback waveFormProtocol.waveformDraw() to do this ) , and my ControllerWaveForm will check some condition, set new params and force WaveFormView redraw by call mWaveFormView.setNeedsDisplay() but as you know it not call draw(_ rect: CGRect) and that is my problem . please tell me if you have any question, thank you so much

@thanhnguyen - it seems to me that you are making an infinite loop.

Somehow draw(_:) is being called initially. This calls the delegate method where you want to call draw(_:) again?

@caroline : it seems you still don’t understand my issue so I decide create simple project to help you easy understand testProject.zip (54.1 KB)

please open ControllerWF class and you can see when I set mWaveForm!.setNeedsDisplay() in delegate , my WaveForm class not call draw(:) ,but If you click play button to set new position and call mWaveForm!.setNeedsDisplay() it work in normal. I don’t understand why ?
thank for your help !!

@thanhnguyen - setNeedsDisplay() does not call draw() immediately. It sets a “dirty” flag of some kind and calls it on the next run loop. However, you are effectively calling setNeedsDisplay() while the runloop is still in draw(), so that when draw() finishes, the “dirty” flag is reset, so draw() will not be called again.

And if the above weren’t the case, you would be creating an infinite loop.

The logic goes something like this. I’m not sure exactly what happens internally, but it would be something like this.

  1. draw() is called when WaveForm is first displayed on the screen.
  2. during draw(), the delegate is not nil, so the callback will be called.
  3. the callback calls setNeedsDisplay(), so the flag is “dirty”, meaning that draw() will be called on the next runloop
  4. draw() finishes and resets the flag to “not dirty” so draw() will not be called on the next runloop.

Calling setNeedsDisplay() during a draw() method will not produce any result. This is why the push button works and the delegate method does not.

So the question,how do I know when runloop finish draw() to set new position and redraw my view automatically ( it’s mean redraw without push button)

@thanhnguyen - what is your intention? Do you want the blue line to move across the screen?

The question you are asking does’t really make sense. Your code shouldn’t be in draw(). You should have some kind of code outside draw() that whenever the x position variable changes, then do a draw. But draw() should only contain drawing. All loop logic should be outside draw().

I’m trying create waveform library. I have audio and I draw waveform of audio on screen. This is what I done

when audio playing, I want to show current position playing. So if current time of audio is 1s I need compute X coordination (something like this X = computeByZooming(time) ) and draw it on screen, when it draw finish, I will check if audio still play I will redraw blue line at new X position(remove blue line previous) and so on… If I don’t know when first line finish draw I can’t draw next line at next position. Because I have just learnt IOS in one month so I haven’t more ideas to resolve this problem if you have one please suggest me and I want to say sorry if I have any stupid questions

@thanhnguyen - no stupid questions :).

You need to rethink the problem. You are thinking “draw line, when finished draw next line, repeat”.

If I understand correctly, what you actually want to happen is “when the current time changes, draw a line”. If the first line hasn’t finished drawing, that’s not important - the important thing is that the line draws in sync with the audio playing.

As to how you would go about that, I’m not sure. You’ll have to see where your app recognises the change of time and do view.setNeedsDisplay() in there. You shouldn’t need a delegate callback from the view to do that. Treat the view as a dumb object with no code in there apart from drawing.

@caroline - After sent one day for researching “how redraw working in IOS” I believe I can’t resolve this task by using draw( : ). Let me show you what I researched and please correct me if I wrong. This is figure show how runloop and redraw work

And it was explained

When an iOS application is launched, it starts a run loop. The run loop’s job is to listen for events, such as a touch. When an event occurs, the run loop then finds the appropriate handler methods for the event. Those handler methods call other methods, which call more methods, and so on. Once all of the methods have completed, control returns to the run loop.
When the run loop regains control, it checks a list of “dirty views” – views that need to be re-rendered based on what happened in the most recent round of event handling. The run loop then sends the drawRect: message to the views in this list before all of the views in the hierarchy are composited together again.

let see how "Run Loop redraws " step work : it have “dirty views” list and each view it will call draw function of that view JUST ONE TIME . I can prove this. In my testProject above, in clickPlay() function I wrote this code
for i in 0 ..< 2 { mWaveForm!.setNewPosition(_position: i) mWaveForm!.setNeedsDisplay() }
how many time was mWaveForm.draw(_ rect: CGRect) called ? it is one time and i value in draw function is 1 not 0. It’s mean Run Loop redraws don’t care how many time a view was require redraw before, it just look at “dirty views” list and find views which need redraw and call draw one time

Conclude : if I want to redraw a view 2 times I need fire event 2 times. Because while audio playing, it doesn’t fire any event so I can’t redraw my waveform

P/S : As you said [quote=“caroline, post:17, topic:14375”]
You’ll have to see where your app recognises the change of time and do view.setNeedsDisplay() in there… And Treat the view as a dumb object with no code in there apart from drawing
[/quote]
I think your suggest is correct but not enough because As you see for loop above, my app recognize the change of i value and I treat my waveForm as “dumb object” but it still not work as my expecting

Finally : please correct me if I wrong and if I right when I say " I believe I can’t resolve this task by using draw( : )", by your experience in IOS, please suggest me another mechanism to resolve this task. Again Thank you so much !!!

@thanhnguyen - yes that is right. I also believe you can’t resolve this task by using draw() in the way you have been describing.

We have a tutorial that draws a custom music progress bar, which is similar to what your app does. It’s a PaintCode tutorial, which is not applicable to you, but the music is played using MPMusicPlayerController. That class gives feedback as to where the current time is. The app in that tutorial uses that current time to draw the correct length of the progress bar (in your case you’d draw the blue line in the calculated position).

I don’t know what class you are using to play your audio, but perhaps looking at the tutorial’s finished app will help. The app has a progress bar view with a value. When that value is changed, it calls setNeedsDisplay() which calls draw() which draws the current length of the progress bar.

The tutorial is here:
https://www.raywenderlich.com/97941/paintcode-swift-tutorial-part-2-custom-progress-bar

You will be able to see from the final sample code that it’s the view controller that sets the value which then calls setNeedsDisplay(). There’s no complication of knowing what the run loop is doing.

@caroline : Finally everything clearly and now I can resolve this task by using draw(). Previous post I said [quote=“thanhnguyen, post:18, topic:14375”]
if I want to redraw a view 2 times I need fire event 2 times
[/quote]

So I ask myself can I fire event without user’s action ( click,scroll… ) and I got answer, I can use scheduledTimer of Timer like this

let timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.clikPlay), userInfo: nil, repeats: true) timer.fire()

func clikPlay() { if isPlay() { mWaveForm!.setNewPosition(_position: audioPlayer.currentTime) mWaveForm!.setNeedsDisplay() } }

after 0.1 time ( I’m not sure that is second or millisecond ), system will fire an event. That is condition to redraw a view, but I thought this isn’t official way and I saw your tutorial link,download code and amazing they use same way !!

I decide to write down here to help another student understand what exactly going on with draw( : ) and If they have problem with draw ( : ) like me, they will know how to resolve. Finally I want to say thank you for your perfect support, @caroline teacher. thank you so so much :+1: :+1: :+1:

1 Like