Статьи

Создание основанного на узле пользовательского интерфейса для iOS с помощью Swift

Я давно являюсь поклонником пользовательских интерфейсов на  основе узлов и кодирую различные реализации, начиная с  моей первой версии Flex в 2008 году и заканчивая моим недавним  приложением для создания изображений Nodality на базе Adobe AIR  для iPad.

Основываясь на моих недавних экспериментах Swift с  UIScrollView  и моей реализации  шаблона Presentation Model , я сейчас создал  функциональную пробную версию калькулятора на основе узлов.  Узловой пользовательский интерфейс, вероятно, не лучшее решение для простого калькулятора, но это отличный способ настроить и запустить пользовательский интерфейс, прежде чем расширять его для других целей. 
Видео выше описывает дизайн взаимодействия. Проще говоря, чтобы создать узел, пользователь делает длинное нажатие на фон. Узлы могут быть либо числовым вводом, либо оператором (например, сложение или вычитание). Чтобы создать отношения между двумя узлами, долгое нажатие на входные узлы переводит приложение в «режим создания отношений», где пользователь выбирает цель.

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

Погружение в код и  контроллер основного представления  выглядит довольно просто: он содержит  панель инструментов  и экземпляр моего BackgroundControl,  который живет внутри  UIScrollView . Есть также некоторые наблюдатели в  модели представления,  которые позиционируют средства визуализации узлов, отключают прокрутку и изменяют цвет фона в ответ на определенные события.

BackgroundControl  содержит экземпляр  BackgroundGrid , который просто рисует сетку, а RelationshipCurvesLayer  , который рисует кривые Безье между связанными узлами. 
Первоначально и  BackgroundGrid,  и  RelationshipCurvesLayer  были расширенными  CALayers,  которые рисовали себя внутри переопределенного  метода drawInContext ()  в ответ на  needsDisplay () . Тем не менее, эта техника была медленной и резкой, вызвала предупреждения памяти и сломала мой iPad. Решением было расширить  CAShapeLayer и сразу  нарисовать сетки и кривые с помощью некоторых открытых методов, установив путь  к классу. имущество.

Сами узлы визуализируются с использованием экземпляров  NodeWidget  , которые добавляются как вспомогательные представления в  BackgroundControl . Они обрабатывают свое собственное движение с помощью  UIPanGestureRecognizer и, с помощью UILongPressGestureRecognizer , информируют модель презентации, когда следует переходить в «режим создания отношений». 

Компонент  панели инструментов  содержит две кнопки для переключения между числовым типом и типом узла оператора и либо диапазон кнопок для выбора оператора, либо простую цифровую клавиатуру (для iPad нет выделенной чисто цифровой клавиатуры).

Таким образом, благодаря небольшому количеству средств визуализации узлов, иерархии фоновых компонентов и панели инструментов шаблон модели представления прекрасно работал для постановки логики приложения. 
Его ключевые свойства — это массив объектов значений узлов,  NodeVO , который моделирует узел, и ссылка на текущий выбранный узел. Он также предоставляет ряд методов, которые вызываются непосредственно компонентами представления, например changeSelectedNodeOperator ()  и  changeSelectedNodeValue (),  которые вызываются панелью инструментов.

Когда оператор или значение узла изменяется,  NodesPM  вызывает рекурсивную функцию  nodeUpdated (),  которая обновляет значение объекта значения узла, затем находит всех потомков этого узла и обновляет их значения:

static func nodeUpdated(node: NodeVO)
    {
        node.updateValue()
        postNotification(.NodeUpdated, payload: node)

        for candidateNode in nodes
        {
            for inputNode in candidateNode.inputNodes
            {
                if inputNode == node && candidateNode.nodeType == NodeTypes.Operator
                {
                    nodeUpdated(candidateNode)
                }
            }
        }

    }

Метод  updateValue ()  внутри узла просто переключается между возможными операторами и обновляет его значение на основе его входных данных:

func updateValue()
    {
        if inputNodes.count >= 2
        {
            let valueOne = inputNodes[0]
            let valueTwo = inputNodes[1]
            
            switch nodeOperator
            {
                case .Null:
                    value = 0
                case  .Add:
                    value = valueOne.value + valueTwo.value
                case .Subtract:
                    value = valueOne.value - valueTwo.value
                case .Multiply:
                    value = valueOne.value * valueTwo.value
                case .Divide:
                    value = valueOne.value / valueTwo.value
            }
        }

    }

Требование Swift для исчерпывающих операторов switch в сочетании с типизированными перечислениями просто фантастическое. Это делает невозможным забыть добавить регистр просто потому, что код не будет компилироваться без него.

Я действительно впечатлен скоростью Swift. Я добавил множество анимаций, чтобы добавить новые элементы управления и анимировать между цветами, есть тени на виджетах узлов, кривые Безье и размытие на панели инструментов, но пользовательский интерфейс плавный и отзывчивый. 

Это проверка концепции, наряду с  экспериментами я недавно цепочкой вместе  CIFilter сек  прокладывает путь для версии 2 Nodality  написано полностью в Swift. Мой следующий шаг — посмотреть на Core Data, чтобы сохранить состояние и позволить пользователю сохранять сети узлов. Лучшее место, чтобы следить за моими успехами — это моя  учетная запись NSFlexMonkey в Twitter.

Весь исходный код этого приложения доступен в  моем репозитории GitHub