Реализация визуального редактора диалоговых деревьев бота в мобильном приложении

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.

Разработка и поддержка любых видов мобильных приложений:

Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

Это лишь некоторые из типы мобильных приложений, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента.

Услуги, которые мы предлагаем
Показано 1 из 1Все 1735 услуг
Реализация визуального редактора диалоговых деревьев бота в мобильном приложении
Сложный
от 1 недели до 3 месяцев
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    792
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    671
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1097
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    969
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    914
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    495

Реализация визуального редактора диалоговых деревьев бота в мобильном приложении

Визуальный редактор диалоговых деревьев — это полноценный graph editor прямо в мобильном приложении. Пользователь перемещает ноды по холсту, соединяет их стрелками, редактирует содержимое каждого узла. Это один из самых технически сложных UI-компонентов в мобильной разработке: бесконечный canvas, gesture-система с несколькими одновременными распознавателями, рендеринг тысяч элементов, undo/redo, сериализация графа.

Фундамент: бесконечный canvas

Основа редактора — бесконечное масштабируемое пространство для размещения нод. Два принципиально разных подхода:

UIScrollView как viewport

На iOS UIScrollView с contentSize намного большим видимой области (например, 5000×5000 pt). Ноды — UIView внутри contentView. Масштабирование через UIScrollView.minimumZoomScale / maximumZoomScale и viewForZooming(in:). Плюс: нативный scroll, momentum, rubber band — всё бесплатно. Минус: при zoom ноды масштабируются вместе с текстом, label'ы становятся слишком маленькими или слишком большими. Фикс — обратная трансформация label'ов: при scrollViewDidZoom применяем CGAffineTransform(scaleX: 1/zoomScale, y: 1/zoomScale) к тексту внутри нод, чтобы шрифт оставался читаемым.

Кастомный canvas с трансформацией координат

Более гибкий подход: canvas — один UIView с CATransform3D трансформацией. UIPinchGestureRecognizer обновляет scale, UIPanGestureRecognizertranslation. Все ноды позиционируются в «мировых» координатах, перевод в экранные — через матрицу трансформации. Это позволяет реализовать zoom-to-fit, zoom-to-selection, snap-to-grid точнее чем через UIScrollView.

Конфликт жестов решается через UIGestureRecognizerDelegate: pan и pinch работают одновременно, но pan на ноде — это drag ноды, pan на пустом месте — панорамирование холста. Hit-test на начало жеста определяет режим.

В Jetpack Compose — Modifier.graphicsLayer { scaleX = canvasScale; scaleY = canvasScale; translationX = offsetX; translationY = offsetY } с pointerInput для обработки pan и pinch через detectTransformGestures.

Рендеринг рёбер (стрелок)

Кривые Безье для соединений между нодами — визуально лучше прямых линий. Кубическая кривая от output handle ноды-источника до input handle ноды-цели:

let path = UIBezierPath()
path.move(to: startPoint)
let controlPoint1 = CGPoint(x: startPoint.x + (endPoint.x - startPoint.x) * 0.5, y: startPoint.y)
let controlPoint2 = CGPoint(x: startPoint.x + (endPoint.x - startPoint.x) * 0.5, y: endPoint.y)
path.addCurve(to: endPoint, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
edgeLayer.path = path.cgPath

Каждое ребро — CAShapeLayer с strokeColor и стрелочным наконечником через дополнительный CAShapeLayer с треугольником или lineCap = .round с кастомным endcap через CAShapeLayer + UIBezierPath.

Обновление рёбер при перемещении ноды: при pan.changed на ноде — пересчитываем все CAShapeLayer.path для входящих и исходящих рёбер. CATransaction.begin(); CATransaction.setDisableActions(true) для мгновенного обновления без implicit animation.

При большом количестве рёбер (50+) — рендеринг через один общий CALayer с draw-методом и setNeedsDisplay() при изменениях. Один большой CGContext.addPath() для всех рёбер быстрее чем 50 отдельных CAShapeLayer.

Интерактивное создание связей

Пользователь тащит от output handle одной ноды к input handle другой — создаётся новое ребро. Реализация:

  1. UILongPressGestureRecognizer (duration 0.0) на handle view — начало drag
  2. При .began — создаём temporaryEdgeLayer, начало которого зафиксировано на handle
  3. При .changed — обновляем конец temporaryEdgeLayer на позицию пальца
  4. Hit-test при каждом .changed: canvas.hitTest(location, with: nil) — если попадаем на input handle — подсвечиваем её
  5. При .ended — если под пальцем input handle — создаём постоянное ребро, иначе удаляем temporaryEdgeLayer

Предотвращение невалидных связей: нельзя соединить output ноды с собой, нельзя создать дублирующую связь, нельзя создать цикл (если граф должен быть DAG). Проверка цикла — DFS из target ноды, проверяем достижимость source ноды. O(V + E) — быстро для типичных диалоговых деревьев.

Редактирование нод

Тап на ноду — открывается редактор содержимого. Для диалогового узла типа «Message»: UITextView с rich text поддержкой для текста сообщения, переключатель типа (текст/медиа/кнопки), список кнопок с возможностью добавить/удалить.

Bottom sheet (iOS 15+ UISheetPresentationController с .medium и .large detents) для редактора — пользователь видит граф и редактор одновременно в medium режиме. При развёртывании до large — граф уходит за sheet. sheetPresentationController.animateChanges { self.detentIdentifier = .large } для программного переключения.

Keyboard avoidance: при появлении клавиатуры нижний padding UIScrollView внутри sheet увеличивается на keyboardFrame.height - sheet.frame.height + sheetMaxHeight. Обрабатывается через UIKeyboardWillShowNotification.

Undo/Redo

Command Pattern — каждое действие (перемещение ноды, создание/удаление ребра, редактирование содержимого) реализуется как объект команды с методами execute() и undo().

protocol GraphCommand {
    func execute()
    func undo()
}

struct MoveNodeCommand: GraphCommand {
    let node: GraphNode
    let fromPosition: CGPoint
    let toPosition: CGPoint
    func execute() { node.position = toPosition }
    func undo() { node.position = fromPosition }
}

UndoManager — встроен в iOS UIKit, работает с registerUndo(withTarget:handler:). Или собственный CommandStack с undoStack: [GraphCommand] и redoStack: [GraphCommand]. Shake-to-undo на iOS работает автоматически с UndoManager при motionBegan(_:with:).

Группировка команд: перемещение нескольких выделенных нод — одна операция undo. UndoManager.beginUndoGrouping() / endUndoGrouping() или wrapper CompositeCommand.

Выделение и multiple selection

Лассо-выделение: pan на пустом пространстве при удержании нажатия — рисует прямоугольник выделения через CAShapeLayer с fillColor = UIColor.blue.withAlphaComponent(0.1) и strokeColor = UIColor.blue. При завершении жеста — проверяем все ноды на пересечение с прямоугольником через node.frame.intersects(selectionRect) в мировых координатах.

Выделенные ноды — визуальное выделение (синяя обводка через layer.borderColor, layer.borderWidth). Pan на выделении перемещает все выделенные ноды одновременно, сохраняя относительные позиции. Delete — удаление всех выделенных нод и инцидентных рёбер.

Сериализация и синхронизация

Граф → JSON с полным описанием нод и рёбер. Автосохранение каждые 30 секунд или при каждом изменении с debounce 2 секунды. На iOS — Combine.debounce(for: .seconds(2), scheduler: RunLoop.main) на publisher изменений графа, затем URLSession.shared.dataTask для отправки на сервер.

Conflict resolution при совместном редактировании: Operational Transform (сложно) или CRDT (Conflict-free Replicated Data Types) — для диалоговых деревьев достаточно Last-Write-Wins на уровне ноды с timestamp сервера. updatedAt: Date на каждой ноде, при merge берём версию с более поздним timestamp.

Срок: от 1 недели до 3 месяцев. WebView-based редактор на React Flow с нативной оберткой — 1–2 недели. Полноценный нативный редактор с кастомным canvas, undo/redo, multiple selection, real-time синхронизацией и всеми типами нод — 1,5–3 месяца.