Работа с JSON

Для работы с форматом JSON во фреймворке Foundation имеется класс NSJSONSerialization. Он позволяет выполнять две основные вещи: переводить объекты в JSON-данные — сериализовать, и наоборот, из JSON-данных получать объекты — десериализовать.

/* 
articles.json format:

[
  {
    "article": {
      "title": "Холст для рисования",
      "url": "http://valery.bashkatov.org/paper/canvasview",
      "date": "2016-01-22"
    }
  },
  {
    "article": {
      "title": "Вызов метода по таймеру",
      "url": "http://valery.bashkatov.org/paper/method-call-by-timer",
      "date": "2016-01-17"
    }
  }
]
*/

import Foundation

typealias Article = [String: [String: String]]!

let url = NSURL(string: "http://valery.bashkatov.org/files/json/articles.json")!
var data = NSData(contentsOfURL: url)!
var articles: [Article]!

do {
    // JSON data deserialization into the object
    articles = try NSJSONSerialization.JSONObjectWithData(data, 
                                       options: NSJSONReadingOptions()) as! [Article]
} catch {
    print(error)
}

for article in articles {
    print("article")
}

if NSJSONSerialization.isValidJSONObject(articles) {
    do {
        // Object serialization into the JSON data
        data = try NSJSONSerialization.dataWithJSONObject(articles, 
                                       options: NSJSONWritingOptions.PrettyPrinted)
    } catch {
        print(error)
    }
}

print(String(data: data, encoding: NSUTF8StringEncoding)!)

Холст для рисования

Рисование фигур внутри UIView производится через задание в подклассе логики методу drawRect. Каждый раз создавать нового наследника непрактично, кроме того, такой способ не отличается гибкостью в плане управления процессом рисования.

Полезно иметь класс-холст. Для себя я разработал CanvasView. С ним рисование сводится к добавлению нужных путей в массив. CanvasPath — банальный наследник UIBezierPath с цветами обводки и заливки.

import UIKit

/**
The `CanvasPath` class extends UIBezierPath by adding stroke and fill colors. 
Used in `CanvasView` class.
*/
class CanvasPath: UIBezierPath {

    // MARK: - Properties
    /**
    The stroke color.
    */
    var strokeColor: UIColor?

    /**
    The fill color.
    */
    var fillColor: UIColor?
}

/**
The `CanvasView` class makes it easy to draw the shapes inside.
*/
class CanvasView: UIView {

    // MARK: - Properties
    /**
    The paths that are be drawn.
    */
    var paths = [CanvasPath]() {
        didSet {
            // Redraw paths
            setNeedsDisplay()
        }
    }

    // MARK: - Initialization
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }

    private func setup() {
        // Set false for default transparent background color.
        opaque = false
    }

    // MARK: - Drawing
    override func drawRect(rect: CGRect) {
        for path in paths {
            if path.strokeColor != nil {
                path.strokeColor!.setStroke()
                path.stroke()
            }
            
            if path.fillColor != nil {
                path.fillColor!.setFill()
                path.fill()
            }
        }
    }
}

Пример использования.

import UIKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let canvas = CanvasView(frame: CGRect(x: 0, y: 0, width: 400, height: 300))

        let borders = CanvasPath()
        borders.strokeColor = UIColor(white: 0.9, alpha: 1)
        borders.moveToPoint(CGPoint(x: 20, y: 50))
        borders.addLineToPoint(CGPoint(x: 350, y: 50))
        borders.addLineToPoint(CGPoint(x: 350, y: 275))
        borders.addLineToPoint(CGPoint(x: 20, y: 275))
        borders.addLineToPoint(CGPoint(x: 20, y: 50))

        let white = CanvasPath(rect: CGRect(x: 20, y: 50, width: 330, height: 75))
        white.fillColor = UIColor.whiteColor()

        let blue = CanvasPath(rect: CGRect(x: 20, y: 125, width: 330, height: 75))
        blue.fillColor = UIColor(red: 0, green: 0.224, blue: 0.651, alpha: 1)

        let red = CanvasPath(rect: CGRect(x: 20, y: 200, width: 330, height: 75))
        red.fillColor = UIColor(red: 0.835, green: 0.169, blue: 0.118, alpha: 1)

        canvas.paths += [borders, white, blue, red]

        view.addSubview(canvas)
    }
}

Скачать CanvasView.zip

Вызов метода по таймеру

Если код нужно выполнить через определенный интервал времени, поможет класс NSTimer и его статический метод scheduledTimerWithTimeInterval.

import UIKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        /* Parameters: 
             1) interval — number of seconds between firings
             2) target — object-owner of the called method
             3) selector — name of the called method
             4) userInfo — additional user info object (may be nil)
             5) repeats — timer is fired once (false) or until invalidated (true)
        */
        NSTimer.scheduledTimerWithTimeInterval(5,
                                               target: self,
                                               selector: "changeBackgroundColor:",
                                               userInfo: nil,
                                               repeats: true)
    }

    func changeBackgroundColor(timer: NSTimer) {
        // Generate random color
        let red = CGFloat(arc4random_uniform(3)) / 2
        let green = CGFloat(arc4random_uniform(3)) / 2
        let blue = CGFloat(arc4random_uniform(3)) / 2
        let alpha = CGFloat(1)
        let color = UIColor(red: red, green: green, blue: blue, alpha: alpha)

        // Animate background color changes
        UIView.animateWithDuration(1, animations: {
            self.view.backgroundColor = color
        })

        print("New background color: \(color)")

        // If the new color is white, then stop the timer
        if color == UIColor(red: 1, green: 1, blue: 1, alpha: 1) {
            timer.invalidate()

            print("The timer is stopped")
        }
    }
}

Размер строки

У строк есть ряд дополнительных методов для отрисовки и вычисления занимаемой на экране области. Показывать текст все же лучше через компоненты графического интерфейса, а вот определение размера может быть полезным.

Метод sizeWithAttributes принимает на вход словарь атрибутов и возвращает прямоугольную область, необходимую для отображения строки с такими параметрами. Допустимые атрибуты: шрифт, толщина обводки, межбуквенный интервал, тень и другие декорации; полный список — в документации.

import UIKit

class ViewController: UIViewController {
    var stringLabel = UILabel()
    var stringSize = UIView()

    let stringAttributes: [String: AnyObject] =
    [
        NSFontAttributeName: UIFont.systemFontOfSize(25),
        NSStrokeWidthAttributeName: 5
    ]

    override func viewDidLoad() {
        super.viewDidLoad()

        stringLabel.frame = CGRect(x: 0, y: 100, width: 300, height: 50)
        stringLabel.backgroundColor = UIColor.yellowColor()
        stringLabel.attributedText = NSAttributedString(string: "Luke, I'm not your father",
                                                        attributes: stringAttributes)

        stringSize.frame = CGRect(x: 0, y: 160, width: 0, height: 0)
        stringSize.backgroundColor = UIColor(white: 0.9, alpha: 1)

        view.addSubview(stringLabel)
        view.addSubview(stringSize)

        stringSize.frame.size = stringLabel.text!.sizeWithAttributes(stringAttributes)
    }
}

Скрытие статусной строки

Статусная строка — это область в верхней части экрана, на которой отображается системная информация.

Sample with status bar

Есть два способа ее скрыть. Первый — глобально для приложения, задав в Info.plist свойства View controller-based status bar appearance = NO и Status bar is initially hidden = YES.

Sample with info.plist attributes

Второй — для отдельного контроллера, определив ему метод prefersStatusBarHidden, возвращающий true.

import UIKit

class ViewController: UIViewController { 
    override func prefersStatusBarHidden() -> Bool {
        return true
    }
}

На экране результат будет одинаковым.

Sample without status bar