My WWDC18 Scholarship Project (Part 1)

Apple just announced WWDC 2019 and I couldn’t be more excited. WWDC is an experience of a lifetime. As an iOS developer and someone who loves software, WWDC gave me a platform to meet and interact with creative and bright developers from around the globe. I learned so many new software tips and concepts from Apple engineers; and it further motivated me to design and create apps for iPhones and iPads.

In this first of two parts series regarding WWDC, I will be talking about my last year’s scholarship submission, how I made it and some useful tips for anyone looking to create Swift Playground for this year’s scholarship. The second part will be all about traveling + packing, lodging, and events that take place around San Jose during WWDC week and all the non technical stuff.

Here is my playground in action:

 

Swift Playground: Passcode

I called my Swift playground, Passcode and it’s available on Github here. I got the idea for this project from iOS itself. I really liked how iOS verifies the passcode as the user enters the last key of the passcode. So I thought why not create a passcode type interface with numbers moving all around the screen and the user has to click the correct circles in correct order to unlock a card about me.

Random Motion of Circles:

The first major thing I had to do was to move the circles all around the screen in a random fashion.  To achieve this first I subclassed UIView (in this it was a UILabel).

public class Circles: UILabel {
        var velocity: CGPoint = .zero
        public var isSelected: Bool = false
        public var num: Int = 0     
    }

The Circles class has three properties, a velocity of type CGPoint – initially X and Y coordinate of which is 0,0. Then there is a ‘isSelected‘ bool variable and then a num which is of type Int.

Next, I made 12 of these circles using a simple for loop:

for var i in 1...12 {

          let randindex = drand48() * 1
          var color : UIColor = .red
           if (randindex < 0.33) { 
              color = color3 
              } else if (randindex > 0.33 && randindex < 0.66) 
              { color = color2 
              } else if (randindex > 0.66 && randindex < 0.99)
              {
              color = color4
            }

         let view = circles(frame: CGRect(x: 200 , y: 200, width: 40, height: 40))
          view.layer.cornerRadius = 20 
          view.layer.masksToBounds = true
          view.isSelected = false
          view.num = i
          view.layer.zPosition = 4
          view.text = "   \(view.num)"
          view.textColor = .white
          view.isUserInteractionEnabled = true
        
          view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap1)))
          view.backgroundColor = color
          view.velocity.x = CGFloat(arc4random_uniform(100) + 50 )/100.0
          view.velocity.y = CGFloat(arc4random_uniform(100) + 50)/100.0
                
          addSubview(view)
          circleviews.append(view as! circles)

In this for loop, I am looping through the code 12 times. The first bit is randomly picking colors for the view from 4 colors that I have defined initially. Then I am creating the circle view and giving it corner radius, setting the .isSelected value to false, giving it num of the current index, i and also the same label text. Then I am adding a gesture recogniser to each view that I am creating. Similarly, I am giving the velocity property of each view a random X and Y value. Once the view has been created and properties set, I am adding it as a subview and appending it to an array called circleviews.

Next I am setting up a CADisplayLink – CADisplayLink is essentially a timer that triggers a function at exact same rate as your display’s refresh rate. This allows for smooth animations.

startTime = CACurrentMediaTime()
let displayLink = CADisplayLink( target: self, selector: #selector(movecircle))
displayLink.add(to: .main, forMode: RunLoop.Mode.common)
self.displayLink = displayLink

Here I am creating a CADisplayLink and each time when it gets triggered, I am calling a movecircle selector function.

@objc func movecircle(_ displayLink: CADisplayLink) {
        var elapsed = CACurrentMediaTime() - startTime
        
        if elapsed > animationLength {
            stopDisplayLink()
            elapsed = animationLength
        }
        for circle in circleviews {
            if (!circle.isSelected)
            {
            circle.center.x += circle.velocity.x
            circle.center.y += circle.velocity.y
            
            if (circle.center.x > 490 || circle.center.x < 0 ) 
             { circle.velocity.x *= -1.0 
             } else if(circle.center.y > 490 || circle.center.y < 150)
            {
                circle.velocity.y *= -1.0
            }
            }
        }
    }

In the movecircle function, initially I am checking for the elapsed time and if elapsed is greater than the animationLength which I set earlier, I stop the animation. If not then in for loop I traverse the circleview array and if the circleview is not isSelected, then I change the circle.center.x to the circle.velocity.x / same with Y coordinate. The circle.velocity.x is randomly generated at the beginning when the view is created so in essence the circle in it’s entire life time goes on a same path it’s just different from other circles around it that gives it the impression of the circles going randomly. Then in the if statement, I am checking if the circle.center.x and circle.center.y has not passed the the superview edges, and if they have then the velocity is reversed by multiplying it with “-1”.

Which Circle should Go Where?

So this is how the circles are moved around the screen in random motion. Next we will talk about how when I click on the circle, it moves to a specific baseCircle and how it knows which one should go where.

Before starting lets talk about the 4 white base circles that always has a random number on it between 1 and 12. These base circles always have a different number to each other and the numbers that been done in previous tries do not appear again. More on this later. Now say that the base circles have been configured and each have different numbers on them. Now when you click the moving circles, you want the circles to move towards these base circles in order of tapping. The circle tapped first goes to the first base circle, the circle tapped second moves to the second base circle and so on. Also if the circle that reaches the base circle turns out to be wrong then the next circle should move to the same location as the previous one did.

This whole thing sounds really complicated but it really isn’t. When the colored circle is tapped, handleTap1 function is triggered.

@objc func handleTap1(sender: UITapGestureRecognizer) {
        let viewtapped = sender.view! as! circles
        viewtapped.isSelected = true
        count = count + 1
        switch count {
        case 1:
            
            interactionDisabledCircle()
            UIView.animate(withDuration: 3, delay: 0.0, options: [], animations: {
                viewtapped.center = CGPoint(x: 170, y: 95) //self.baseCircles[self.count - 1].center
            }, completion: { (finished: Bool) in
                if (viewtapped.num == self.baseCircles[self.count - 1].baseNum)
                {
                   
                    
                    self.playSound()
                    self.correctPlayer?.play()
                    
                    self.circleSet.append(viewtapped as! circles)
                    self.turncount = self.turncount + 1
                    self.interactionEnabledCircle()
                } else {
                    
                    viewtapped.isSelected = false
                    
                    DispatchQueue.global(qos: .background).async {
                        self.playWrongSound()
                        self.player?.play()
                    }
                    viewtapped.center = self.randomCoordinates()
                    self.count = self.count - 1
                    self.interactionEnabledCircle()
                }
            }).....

Now I will just list 1 out of the 4 cases here but they all are pretty similar. When the first colored circle is tapped, a count variable increments, and the circle.isSelected property is set true. In the switch case I am checking the count variable. If it’s 1 that means it’s the first circle that the user tapped. Next, we animate the movement of the circle as the circle.center moves to the baseCircle.center. Before all this I am disabling any gesture recognition on the moving circles so that it doesn’t become a mess. Once the animation is completed, in the completion handler, I am checking if the number on the colored circle is same as the one on the baseCircle. If it is then a correct sound is played and the tappedview / or the colored circle is added to a circleSet array which are then later removed as the game continues and then I enabled all the interactions. However, if the  number is not same on the baseCircle and the colored circle then the .isSelected property is set to false, a wrong sound is played and the center of the colored circle is given a random center coordinate and the count which was initially incremented is decremented.

This is how the circles know exactly where they need to go.

BaseCircles Situation:

Now as for the baseCircles, how the baseCircles know which numbers are already done and how I reload them once all the 4 circles have been correctly filled?

The baseCircles are made in similar fashion as the coloredCircles, only thing which is different is that for each of the 4 circles that are created, the number on them are checked before being added:

  var rn = randomNumber()           
  while(baseNumbersArray.contains(Int(rn)))
  {
   rn = randomNumber()
   }
   base.baseNum = Int(rn)
   baseNumbersArray.append(Int(rn))

In the for loop in which I am creating these random circles, I am generating a random number between 1 – 12 and initially checking if its in baseNumberArray which is initially empty but the generated number is appended to the array as more and more base circles are created. If the rn, or random number exists in the array then a new randomNumber is generated until a new once comes about which is not present in the baseNumbersArray.

So thats essentially how this works. To show cards that appear after the 4 circles are correctly filled, all I am doing is running a function at case 4 of the switch statement in handleTap1 function which I talked about earlier.

So this was my project. You can get the entire code for this on my GitHub.

Tips:

  • Make sure to use latest Apple technologies but it’s not compulsory. I did not use any latest iOS technologies in this project. However it will surely help
  • Keep it simple.
  • It’s all about creativity. Express yourself in the Swift Playground. Don’t worry if you think it’s way too simple or novice. If you think it will work well then go for it. It’s all about how you present your idea.
  • Enjoy the process!

Part 2: Will be all about traveling, lodging, the conference, WWDC parties, San Jose, Steve Jobs Theatre & lots more!

Leave a Reply

Your email address will not be published. Required fields are marked *