Следуя моим недавним публикациям о  3D Touch  и  объединении касаний , объединение этих двух вещей в простом приложении для рисования казалось очевидным следующим шагом. Это также дает мне возможность повозиться с тем,  CIImageAccumulator что было недавно представлено в iOS 9.
Мое небольшое демонстрационное приложение ForceSketch позволяет пользователю рисовать на экране iPhone 6. И вес линии, и цвет линии связаны с давлением прикосновения. Как и в моей демонстрации ChromaTouch , давление контролирует оттенок, поэтому самое легкое касание — красный, поворачивающийся к зеленому при трети максимального давления, к синему при двух третях и обратно к красному при максимальном давлении.
Как только пользователь поднимает палец, два фильтра Core Image, CIColorControls и CIGaussianBlur включаются и исчезают из рисунка.
Механика рисования ForceSketch
Код рисования все вызывается из  метода моего  контроллера представления touchesMoved . Именно здесь я создаю  UIImage экземпляр, основанный на объединенных касаниях и составном изображении поверх существующего аккумулятора изображений. В производственном приложении я бы, вероятно, применил фильтрацию изображений в фоновом потоке, чтобы улучшить производительность пользовательского интерфейса, но, для этой демонстрации, я думаю, что этот подход в порядке.
Вводное  guard утверждение гарантирует, что у меня есть необязательные константы для наиболее важных элементов:
guard let touch = touches.first,
        event = event,
        coalescedTouches = event.coalescedTouchesForTouch(touch) else
    {
        return
    }
Следующим шагом является подготовка к созданию объекта изображения. Для этого мне нужно начать контекст изображения и создать ссылку на текущий контекст:
    UIGraphicsBeginImageContext(view.frame.size)
    let cgContext = UIGraphicsGetCurrentContext()
Чтобы обеспечить максимальную точность жеста пользователя, я зацикливаюсь на объединенных касаниях — это дает мне все промежуточные касания, которые могли произойти между вызовами touchesMoved ().
    for coalescedTouch in coalescedTouches {
Используя  force свойство каждого касания, я создаю константы для цвета и веса отрезков. Чтобы убедиться, что пользователи не-3D Touch устройств звонят, все еще используют приложение, я проверяю  forceTouchCapability и даю этим пользователям фиксированный вес и цвет:
    let lineWidth = (traitCollection.forceTouchCapability == UIForceTouchCapability.Available) ?
        (coalescedTouch.force / coalescedTouch.maximumPossibleForce) * 20 :
        10
    let lineColor = (traitCollection.forceTouchCapability == UIForceTouchCapability.Available) ?
        UIColor(hue: coalescedTouch.force / coalescedTouch.maximumPossibleForce, saturation: 1, brightness: 1, alpha: 1).CGColor :
        UIColor.grayColor().CGColor
С помощью этих констант я могу установить ширину линии и цвет обводки в графическом контексте:
    CGContextSetLineWidth(cgContext, lineWidth)
    CGContextSetStrokeColorWithColor(cgContext, lineColor)
… и теперь я готов определить начало и конец моего отрезка для этого слияния:
    CGContextMoveToPoint(cgContext,
        previousTouchLocation!.x,
        previousTouchLocation!.y)
    CGContextAddLineToPoint(cgContext,
        coalescedTouch.locationInView(view).x,
        coalescedTouch.locationInView(view).y)
Заключительные шаги внутри объединенного цикла касаний должны пройти путь и обновить  previousTouchLocation:
   CGContextStrokePath(cgContext)
    previousTouchLocation = coalescedTouch.locationInView(view)
После того как все штрихи были добавлены в графический контекст, для создания UIImage экземпляра требуется одна строка кода,  а затем конец контекста:
let drawnImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
Отображение нарисованных линий
Чтобы отобразить только что нарисованные линии  drawnImage, я использую  CISourceOverCompositing фильтр с изображением  drawnImage в качестве переднего плана и текущее изображение аккумулятора изображения в качестве фона: 
compositeFilter.setValue(CIImage(image: drawnImage),
        forKey: kCIInputImageKey)
    compositeFilter.setValue(imageAccumulator.image(),
        forKey: kCIInputBackgroundImageKey)
Затем возьмите выходные данные источника через композитор, передайте их обратно в аккумулятор и заполните  UIImageView их изображением аккумулятора:
   imageAccumulator.setImage(compositeFilter.valueForKey(kCIOutputImageKey) as! CIImage)
    imageView.image = UIImage(CIImage: imageAccumulator.image())
Blurry Fade Out
Как только пользователь поднимает палец, я делаю «размытое затухание» нарисованного изображения. Этот эффект использует два фильтра Core Image, которые определены как константы:
 let hsb = CIFilter(name: "CIColorControls",
        withInputParameters: [kCIInputBrightnessKey: 0.05])!
    let gaussianBlur = CIFilter(name: "CIGaussianBlur",
        withInputParameters: [kCIInputRadiusKey: 1])!
Первая часть эффекта заключается в использовании a,  CADisplayLink который будет вызываться  step() при каждом обновлении экрана:
let displayLink = CADisplayLink(target: self, selector: Selector("step"))
    displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode)
I rely on previousTouchLocation being nil to infer the user has finished their touch. If that’s the case, I simply pass the accumulator’s current image into the HSB / colour control filter, pass that filter’s output into the Gaussian Blur and finally the blur’s output back into the accumulator:
hsb.setValue(imageAccumulator.image(), forKey: kCIInputImageKey)
    gaussianBlur.setValue(hsb.valueForKey(kCIOutputImageKey) as! CIImage, forKey: kCIInputImageKey)
    imageAccumulator.setImage(gaussianBlur.valueForKey(kCIOutputImageKey) as! CIImage)
    imageView.image = UIImage(CIImage: imageAccumulator.image())
Source Code
As always, the source code for this project is available in my GitHub repository here. Enjoy!
