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

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, система з кількома одночасними розпізнавачами, рендеринг тисяч елементів, undo/redo, сериалізація графа.

Фундамент: нескінченний canvas

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

UIScrollView як viewport

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

Кастомний canvas з трансформацією

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

Конфлікт жестів вирішується через 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 — граф ховається. 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, працює з registerUndo(withTarget:handler:). Або власний CommandStack з undoStack: [GraphCommand] та redoStack: [GraphCommand]. Shake-to-undo на iOS працює автоматично з UndoManager при motionBegan(_:with:).

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

Виділення та множинне виділення

Лассо-виділення: 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 на рівні вузла з server timestamp. updatedAt: Date на кожній вузлі, при merge беремо версію з більш пізнім timestamp.

Терміни: від 1 тижня до 3 місяців. WebView-based редактор на React Flow з нативною обертткою — 1–2 тижні. Повнофункціональний нативний редактор з кастомним canvas, undo/redo, множинним виділенням, real-time синхронізацією та всіма типами вузлів — 1,5–3 місяці.