Если вы создаете приложение для рисования или рисования для iOS, вы можете вспомнить из моего недавнего поста, в котором обсуждались объединенные касания, способ повысить разрешение касания и получить доступ к промежуточным местоположениям касания, которые могли произойти между вызовами. Однако, если ваше приложение просто рисует прямые линии между каждым местом касания, даже слитными, и ваш пользователь быстро перемещает свой палец или карандаш, они увидят, что их рисунок отображается в виде отрезков.
Есть лучшее решение: использовать сплайн-интерполяцию для рисования кривых Безье между каждым касанием и визуализации единой непрерывной кривой. В прошлом я обсуждал сплайн-интерполяцию при рисовании кривой для прохождения всех контрольных точек фильтра Core Image Tone Curve, и этот проект заимствует этот код, но повторно использует его для приложения для рисования:
Мое демонстрационное приложение предоставляет пользователю два блока, в каждый из которых он может рисовать рисунок, который отражается в другом блоке. Рамка слева отображает их чертеж с использованием сплайн-интерполяции, а рамка справа — с прямыми линиями для определения местоположения касания и касания местоположения (хотя и со сращенным касанием). Сразу видно, насколько красивее рисунок слева, представленный в виде одной кривой.
Основы SmoothScribble
Оба поля на экране — это подклассифицированные экземпляры ScribbleView, которые соответствуют Scribblable. Класс ScribbleView — это просто UIView, который содержит два дополнительных CAShapeLayer — backgroundLayer для отображения «исторических» каракулей и «рабочий» DrawingLayer для отображения текущего, в процессе выполнения каракулей.
Протокол Scribblable содержит три метода, вызываемых в начале, во время и в конце жеста каракулей, и метод для его очистки.
func beginScribble(point: CGPoint)
func appendScribble(point: CGPoint)
func endScribble()
func clearScribble()
Класс SimpleScribbleView
Давайте сначала посмотрим на простую реализацию. SimpleScribbleView будет рисовать прямые линии, начиная с точки, установленной byginScribble ():
let simplePath = UIBezierPath()
func beginScribble(point: CGPoint)
{
simplePath.moveToPoint(point)
}
Затем добавьте линии в его simplePath и обновите путь слоя чертежа при каждом движении:
func appendScribble(point: CGPoint)
{
simplePath.addLineToPoint(point)
drawingLayer.path = simplePath.CGPath
}
Наконец, когда пользователь убирает палец с экрана, simplePath добавляется к любому существующему фоновому пути («историческим» каракулям) и затем очищается:
func endScribble()
{
if let backgroundPath = backgroundLayer.path
{
simplePath.appendPath(UIBezierPath(CGPath: backgroundPath))
}
backgroundLayer.path = simplePath.CGPath
simplePath.removeAllPoints()
drawingLayer.path = simplePath.CGPath
}
В результате, как вы можете видеть выше, серия прямых линий с артефактами линий выглядит тем хуже, чем быстрее пользователь перемещает свой палец.
Класс HermiteScribbleView
Сплайн-интерполированная версия немного отличается. Здесь, в дополнение к UIBezierPath для хранения текущего наброска, есть также массив всех точек, из которых состоит путь, и он должен будет интерполироваться:
let hermitePath = UIBezierPath()
var interpolationPoints = [CGPoint]()
Когда beginScribble () вызывается в HermiteScribbleView, он создает новый массив этих точек интерполяции и заполняет его начальной позицией:
func beginScribble(point: CGPoint)
{
interpolationPoints = [point]
}
Теперь при каждом перемещении он добавляет новую точку в массив interpolationPoints и использует написанное мной расширение для UIBezierPath с именем interpolatePointsWithHermite (), чтобы построить серию кривых Безье, чтобы получить этот гладкий эрмитовый интерполированный сплайн между всеми точками:
func appendScribble(point: CGPoint)
{
interpolationPoints.append(point)
hermitePath.removeAllPoints()
hermitePath.interpolatePointsWithHermite(interpolationPoints)
drawingLayer.path = hermitePath.CGPath
}
Наконец, endScribble () из HermiteScribbleView делает почти то же самое, что и его более простой брат, добавляя копию своего «рабочего» слоя к своему «историческому» слою:
func endScribble()
{
if let backgroundPath = backgroundLayer.path
{
hermitePath.appendPath(UIBezierPath(CGPath: backgroundPath))
}
backgroundLayer.path = hermitePath.CGPath
hermitePath.removeAllPoints()
drawingLayer.path = hermitePath.CGPath
}
Подключение SimpleScribbleView и HermiteScribbleView
Контроллер основного вида использует UIStackView, чтобы расположить два вида каракулей рядом друг с другом в пейзаже выше и ниже друг друга в портретной ориентации.
Приступая к работе, я выяснил, какой из двух представлений является источником, и установил для touchOrigin:
if(hermiteScribbleView.frame.contains(location))
{
touchOrigin = hermiteScribbleView
}
else if (simpleScribbleView.frame.contains(location))
{
touchOrigin = simpleScribbleView
}
else
{
touchOrigin = nil
return
}
С этим набором я могу использовать locationInView касания для touchOrigin, чтобы применить одну и ту же информацию касания к обоим представлениям.
Вы, возможно, уже догадались, что так же touchesBegan () контроллера представления вызывает beginScribble ():
if let adjustedLocationInView = touches.first?.locationInView(touchOrigin)
{
hermiteScribbleView.beginScribble(adjustedLocationInView)
simpleScribbleView.beginScribble(adjustedLocationInView)
}
AppendScribble () вызывается внутри touchesMoved () контроллера представления:
coalescedTouches.forEach
{
hermiteScribbleView.appendScribble($0.locationInView(touchOrigin))
simpleScribbleView.appendScribble($0.locationInView(touchOrigin))
}
Который просто оставляет touchesEnded () для вызова endScribble ():
hermiteScribbleView.endScribble()
simpleScribbleView.endScribble()
В заключении
Независимо от того, насколько быстро работает ваш код, есть большая вероятность, что пальцы вашего пользователя быстрее. Если у вас есть приложение для рисования, плавная интерполяция жеста пользователя, а не просто рисование прямых линий между каждым касанием, делает их рисунки намного более естественными и, возможно, ближе к изображению, которое они имели в виду.
Как всегда, исходный код этого небольшого демонстрационного приложения доступен в моем репозитории GitHub здесь . Наслаждайтесь!