[Swift 1] Day 6-7 - App Challenge - Word Magnets


#66

This is my approach:

All actions are gestures besides input new word/modify old word.

UIPanGestureRecognizer @ wordLabel: Drag word

UITapGestureRecognizer (2 tap) @ imageView: Add new word
UITapGestureRecognizer (2 tap) @ wordLabel: Modify old word
UITapGestureRecognizer (1 tap) @ maskView: Cancel modification

UILongPressGestureRecognizer @ imageView: Randomize word labels

UISwipeGestureRecognizer (Left / Right) @ imageView: Change background image

Every word label’s background was customized, you can see the label’s shape was not perfect rectangle.


#67

https://www.youtube.com/watch?v=rC34bb5m-f0&feature=youtu.be

Just a plain UI design :grin:

  • I decide the app for kids so it come with some cartoon characters that kids can play with. (cartoon image just use for example)
  • Input words into textField to add new words.
  • Edit words by press Edit Word button, this only edit added last word or last member in array.
  • Refresh screen each time add new words or edit words by random all word labels and cartoon images.
  • Click on colors button to change word label background color by random and also show random color on button background color.

still have many thing to do to make it more easy to play.

code


#68

Hi Paul,

when I came to day 18/19 it got a bit too advanced for me, so I decided to go through some of the older sessions again. For example, the first time I went through the word magnets session I couldn’t understand all parts of it. Now I could completely write the code myself and decided to try to make it a bit more advanced.

I did look at some of the examples of the other challenges as you might see but I did write it myself. With the exception of the “removal Tag” option of Fernando (so fernando thanks for that :slight_smile: ). I will put some screenshots below but basically the app starts with a default set of words, when you click new, the text field appears and the focus is set to it so that you can type immediately. When you press “return” the word gets added to the screen with a different colour and the textfield disappears again. When reset is clicked all ‘new’ words with the removal tag are removed from the superview.


Only thing I might like to add as well is the possibility to have the labels “connect” to each other. Is that really difficult to achieve or can you get me in the right direction for this?

My code of the view controller is shown below.

===============================================================================

//
// ViewController.swift
// Word Magnets
//
// Created by Nico van der Linden on 23/07/15.
// Copyright © 2015 Noki-Online. All rights reserved.
//

import UIKit

class ViewController: UIViewController, UITextFieldDelegate {

@IBOutlet weak var labelView: UIView!
@IBOutlet weak var addLabelTextfield: UITextField!
var wordArray = ["I", "like", "to", "eat", "fruit", "my", "favorite"]
var labelViewWidth: UInt32!
var labelViewHeight: UInt32!
let tagForRemoval = 88

func textFieldShouldReturn(textField: UITextField) -> Bool {
    if textField == addLabelTextfield {
        wordArray.append(addLabelTextfield.text)
        println(wordArray)

        if let word = wordArray.last {
            addLabel(word, new: true)
        }
        
        addLabelTextfield.alpha = 0
        addLabelTextfield.text = ""
        addLabelTextfield.resignFirstResponder()
    }
    return true
}

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    
    labelView.clipsToBounds = true
    
    labelViewWidth = UInt32(labelView.bounds.size.width)/2
    labelViewHeight = UInt32(labelView.bounds.size.height)/2

    println("width: \(labelViewWidth)")
    println("height: \(labelViewHeight)")
    
    for word in wordArray {

        addLabel(word, new: false)
    }
    
}

func addLabel(word: String, new: Bool) {
    var label = UILabel()
    var x = CGFloat(arc4random_uniform(labelViewWidth))
    var y = CGFloat(arc4random_uniform(labelViewHeight))
    println("x: \(x)")
    println("y: \(y)")
    
    label.text = word
    label.font = UIFont.systemFontOfSize(20)
    
    label.sizeToFit()
    
    label.center = CGPoint(x: x, y: y)
    
    if new == false {
        label.backgroundColor = UIColor.whiteColor()
    }
    else {
        label.backgroundColor = UIColor.cyanColor()
        label.tag = tagForRemoval
    }
    
    labelView.addSubview(label)
    
    var panGesture = UIPanGestureRecognizer(target: self, action: "handlePanGesture:")
    label.addGestureRecognizer(panGesture)
    label.userInteractionEnabled = true

}

func handlePanGesture(panGesture: UIPanGestureRecognizer) {
    
    var translation = panGesture.translationInView(view)
    panGesture.setTranslation(CGPointZero, inView: view)
    var label = panGesture.view as! UILabel
    
    label.center = CGPoint(x: label.center.x + translation.x, y: label.center.y + translation.y)

}

func removeLabels() {
    for subView in labelView.subviews {
        if let label = view.viewWithTag(tagForRemoval) {
            label.removeFromSuperview()
        }
    }
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

override func preferredStatusBarStyle() -> UIStatusBarStyle {
    return UIStatusBarStyle.LightContent
}

@IBAction func ChangeColorPressed(sender: UIButton) {
    
    let colorButton = sender
    labelView.backgroundColor = colorButton.backgroundColor
}

@IBAction func newLabelPressed(sender: UIButton) {
    
    addLabelTextfield.alpha = 1
    addLabelTextfield.becomeFirstResponder()
}

@IBAction func resetButtonPressed(sender: UIButton) {
    addLabelTextfield.alpha = 0
    labelView.backgroundColor = UIColor.lightGrayColor()
    removeLabels()
    
}

}

Thanks again for the good video’s!

Kind Regards,
Nico van der Linden


#69

Great work going back to the older lessons!

Day 18/19 are a little more complex, did you get through any of the videos? It’s a special technique used for multiple screens, and little “widgets” that you can reuse.

How do you want labels to stick together? Can you explain the way it would work?


#70

Hi Paul,

Yes I did get some of the videos but noticed that I have some knowledge gaps for which I need to go to some of the previous videos. But I am getting there :smile:

What I mean with the additional feature for the magnetic words is that it would be nice if you could ‘snap’ the words together somehow when they get close to each other (if that isn’t too hard to achieve)?

Kind regards,
Nico


#71

Someone already did that, don’t remember who ( it was a while ago ) , you need to basically do that at the point when the user release the word. Basically, when the moving is over. You will make a new function that will enumerate all the children of the view ( the labels ) and then will find the closest one.
Then you need to set treshholds for distance, basically - you will only snap if you are < 50px away and you will snap to 10px. The rest is just making an animating move, like in the first lessons.
Make sure you add all the weird cases, like when the word won’t fit because of the end of the screen for example…easier if you do it on paper first.


#72

@nicolinden @ravenshore @Paradisiak

Alexandre added a special area to drag and drop words to arrange in Casual Poet (on the App Store).


#73

Hello everyone.

My Word Magnets app is finally done. I did my best (I am total beginner) and here’s the result. Any advice or criticism is appreciated.

Main features :

  • change background color to random color using slider
  • add words on screen
  • hide keyboard after adding word
  • move labels on screen using pan

Thank you

You can download whole project here.

Best regards
Tomas


#74

@Tomas Great work!

Can you post screenshots of it in action so we can see what it looks like?

(Dropbox links work too)


#75

Thank you Paul :slight_smile: I am really happy I actually made something that works and I am so motivated right now :slight_smile: Great course Paul, thank you.

Unfortunately, as new forum user I am not allowed to post screenshots. So please download screenshots from my Dropbox.

Best regards and nice weekend :slight_smile:


#76

great work!

I think you should be able to post pictures now.


#77

OK :smiley: Testing screenshot posting


Yes !!! It works !!! Thanks Paul :smiley:


#78

great!

Challenge 1: Try playing with the font and font size.

Challenge 2: See if you can customize in the app using a UIPicker or a UITableView … see my font tutorial (Objective-C so it’s slightly different).

UITableView in Swift: http://www.ralfebert.de/tutorials/ios-swift-uitableviewcontroller/
Fonts in Swift: http://giordanoscalzo.tumblr.com/post/95900320382/print-all-ios-fonts-in-swift


#79

Hi Paul,

Hope all is well. Am doing the challenge and trying to add physics to the labels. Got it kind of working but am struggling with collision boundaries. Funnily, the last word in the array “favourite” seems to be doing exactly what I want and collides with the set boundaries. The other labels simply fly off the screen and are not stopped even though when I print(animator?.behaviors) --> it shows the same behaviours are active compared with the “favourite” label…

//

// ViewController.swift
// Word Magnet
//
// Created by Maximilian Doelle on 21/10/2015.
// Copyright © 2015 Maximilian Doelle. All rights reserved.
//

import UIKit

class ViewController: UIViewController, UITextFieldDelegate {

var animator: UIDynamicAnimator?
var gravity: UIGravityBehavior?
var collision: UICollisionBehavior?

var globalEntryLabel : String = "entrytext"


//Generate new word magnet
func userGeneratedLabel() {
    var labelUG = UILabel()
    labelUG.text = globalEntryLabel
    labelUG.font = UIFont.systemFontOfSize(32)
    labelUG.sizeToFit()
    labelUG.center = CGPoint(x: 300, y: 200)
    labelUG.backgroundColor = UIColor.whiteColor()
    var x = CGFloat(arc4random_uniform(300))
    print("\(x)")
    var y = CGFloat(arc4random_uniform(300))
    labelUG.center = CGPoint(x: x, y: y)
    view.addSubview(labelUG)
    print(entryLabel.text)
    
    var pangesture = UIPanGestureRecognizer(target: self, action:"handlePanGesture:")
    pangesture.minimumNumberOfTouches = 1

    labelUG.addGestureRecognizer(pangesture)
    labelUG.userInteractionEnabled = true
}



override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    
    NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWasShown:"), name:UIKeyboardWillShowNotification, object: nil);
    
    
    var wordArray = ["I","like","to","eat","fruit","my","favourite"]
    
    view.backgroundColor = UIColor.blackColor()
    
    for word in wordArray {
        
        var label = UILabel()
        label.text = word
        
        label.font = UIFont.systemFontOfSize(32)
        
        label.sizeToFit()
        
        label.center = CGPoint(x: 300, y: 200)
        label.backgroundColor = UIColor.whiteColor()
        
        var x = CGFloat(arc4random_uniform(300))
        print("\(x)")
        var y = CGFloat(arc4random_uniform(300))
        
        label.center = CGPoint(x: x, y: y)
        
        
        view.addSubview(label)
        
        //add physics
        
        self.animator = UIDynamicAnimator(referenceView: self.view);
        
        // Instantiates the Gravity Behavior and assigns the box to it
        self.gravity = UIGravityBehavior(items: [label]);
        
        // Instantiates the Collision Behavior and assigns the box to it
        
        self.collision = UICollisionBehavior(items: [label]);
        collision!.setTranslatesReferenceBoundsIntoBoundaryWithInsets(UIEdgeInsets(top: 0, left: 400, bottom: 0, right: 400))
        //self.collision!.translatesReferenceBoundsIntoBoundary = true;
        
        //Add Gravity and Collision
        self.animator!.addBehavior(self.gravity!)
        self.animator!.addBehavior(self.collision!)
        
        // Pan Gesture 
        
        var pangesture = UIPanGestureRecognizer(target: self, action:"handlePanGesture:")
        pangesture.minimumNumberOfTouches = 1
        label.addGestureRecognizer(pangesture)
        label.userInteractionEnabled = true
        }
}

func handlePanGesture(panGesture: UIPanGestureRecognizer!){
    //print("handlePanGestureTapped")
        
    //get translation
        var translation = panGesture.translationInView(view)
        panGesture.setTranslation(CGPointZero, inView: view)
        
    // add dx,dy to current label position
        var label = panGesture.view as! UILabel
        label.center = CGPoint(x: label.center.x + translation.x, y: label.center.y + translation.y)
        
    //add begin gesture for phyics
        var location = panGesture.locationInView(self.view);
        var touchLocation = panGesture.locationInView(label);
        
        if panGesture.state == UIGestureRecognizerState.Began {
    // Do some initial setup here
        print(animator?.behaviors)
    //print("Reference boundaries state began:\(collision?.boundaryIdentifiers)")
            self.animator!.removeAllBehaviors()

     // Will set the box's center to the location value stored above
            label.center = location;

// var offset = UIOffsetMake(touchLocation.x - CGRectGetMidX(label.bounds), touchLocation.y - CGRectGetMidY(label.bounds))
// self.animator!.attach = UIAttachmentBehavior(item: self.label, offsetFromCenter: offset, attachedToAnchor: location)

        } else if panGesture.state == UIGestureRecognizerState.Changed {
label.center = location;
}else if panGesture.state == UIGestureRecognizerState.Ended {
// Handles what should happen when the box is released...
print("box is released")

            
var itemBehavior = UIDynamicItemBehavior(items: [label]);
itemBehavior.addLinearVelocity(panGesture.velocityInView(self.view), forItem: label);
itemBehavior.angularResistance = 0;
itemBehavior.elasticity = 0.8;
self.animator!.addBehavior(itemBehavior);
self.animator!.addBehavior(self.gravity!)
self.animator!.addBehavior(self.collision!)

// print(“Reference boundaries state ended:(collision?.boundaryIdentifiers)”)
print(animator?.behaviors)
}

}

@IBOutlet weak var bottomConstraint: NSLayoutConstraint!
@IBOutlet weak var entryLabel: UITextField!

//Move label when keyboard is shown

func keyboardWasShown(notification: NSNotification) {
    print("keyboard was shown")
    var info = notification.userInfo!
    var keyboardFrame: CGRect = (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
    
    UIView.animateWithDuration(0.1, animations: { () -> Void in
        self.bottomConstraint.constant = -20 - keyboardFrame.size.height

    })
}

//Press enter and create label

func textFieldShouldReturn(textField: UITextField) -> Bool {
    textField.resignFirstResponder()
    print("pressed enter")
    print(entryLabel.text)
    userGeneratedLabel(
        globalEntryLabel = entryLabel.text!
    )
    self.bottomConstraint.constant = -20
    entryLabel.text=""
    
    return true;
}



override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

}


#80

@Philomath Can you upload the entire folder as a .zip using Dropbox or another file sharing service?

Hard to see what the problem is, without being able to run it.


#81

Hi Paul,

Sure!

I have now tried to narrow down the problem and it seems that the arrays is not the culprit but somehow only the last object holds the physics attributes when loading the view…

Here’s the link: https://www.dropbox.com/sh/kkcp2xrrpkh44ma/AAAQbOQEsMU2-fbZ1c0uS5_ta?dl=0

Thanks!


#82

Sorry for the delay @Philomath two images you included are not in your .ZIP so I can’t run the code.

Can you re-add the images and make sure to copy them into the project?


#83

No worries @PaulSolt - thanks for taking a look at it!

I have updated the original Dropbox Link and included the pictures.

Thanks and happy holidays,

Max


#84

@Philomath , just glancing through your code here I think the problem is here
self.gravity = UIGravityBehavior(items: [label]);

Every time you call this line you are overwriting the gravity array, and setting it to the current Label.
Instead you should put all the labels in a separate array and do something like:
self.gravity = UIGravityBehavior(items: [arrayOfLabels])

That’s why you’ll always have only the last label in it, cause you are overwriting it.


#85

Thanks @ravenshore - it is working :smile:

Now only have to figure out how to only remove a specifics label’s UIDynamicItemBehavior when the penGesture has started versus currently removing allBehaviours altogether…

Thanks and happy holidays!

Max