אנו מפתחים עורכי עצי דיאלוג חזותיים עבור אפליקציות מובייל — אחד מרכיבי ה-UI המאתגרים ביותר טכנית. אנחנו מדברים על קנבס אינסופי, מערכות מחוות ריבוי-מגע עם מזהים סימולטניים, רינדור של אלפי אלמנטים, ביטול/ביצוע מחדש מלא, וסריאליזציה של גרפים. במהלך 5+ השנים האחרונות השקנו יותר מ-50 עורכים כאלה עבור צ'אטבוטים בבנקאות, מסחר אלקטרוני ותמיכה. המהנדסים שלנו שולטים באותה מידה בערימות טבעיות (Swift, Kotlin) ובמסגרות חוצות-פלטפורמות (Flutter, React Native). אנו מבטיחים התנהגות נכונה בכל מכשירי היעד ועמידה בהנחיות App Store ו-Google Play. הבונה החזותי שלנו שימש 20+ חברות.
מקור: תקני תעשייה לעורכי דיאלוג (W3C Dialog Technologies)
עורך עצי דיאלוג חזותי: יישום קנבס אינסופי
הבסיס של העורך הוא מרחב אינסופי וניתן לקנה מידה להצבת צמתים. שתי גישות שונות מהותית מושוות להלן.
השוואת גישות קנבס
| קריטריון | UIScrollView כ-viewport | קנבס מותאם אישית עם transform |
|---|---|---|
| פשטות היישום | גבוהה, גלילה טבעית | בינונית, טיפול ידני במחוות |
| קנה מידה של טקסט | בעייתי: גופנים מתקשקשים | קבוע באמצעות transform הפוך |
| גמישות התאמה לזום | מוגבלת | מלאה, באמצעות מטריצה |
| ביצועים (100+ צמתים) | ממוצע | גבוה (CALayer יחיד) |
ב-iOS, UIScrollView עם contentSize 5000×5000 pt הוא התחלה פשוטה. צמתים הם UIView בתוך contentView. זום באמצעות minimumZoomScale/maximumZoomScale. חיסרון: טקסט הופך לבלתי קריא בעת זום. פתרון: הפוך את ה-transform על תוויות ב-scrollViewDidZoom. קנבס מותאם אישית באמצעות CATransform3D נותן שליטה רבה יותר: התאמה לזום, snap-to-grid. קונפליקטים של מחוות נפתרים עם UIGestureRecognizerDelegate: pan על צומת = גרירה, pan על שטח ריק = פאן.
ב-Jetpack Compose, השתמש ב-Modifier.graphicsLayer עם pointerInput עבור detectTransformGestures.
למה ניהול מחוות נכון חשוב
העורך משתמש בו-זמנית ב-pan, pinch, לחיצה ארוכה וטאפ. ללא תעדוף ברור, פעולות משתמש יתפרשו לא נכון. ב-iOS אנו משתמשים ב-UIGestureRecognizerDelegate עם shouldRecognizeSimultaneouslyWith עבור pan ו-pinch, ובדיקת פגיעה בתחילת המחווה קובעת אם צומת או קנבס נגררים. ב-Android אנו משתמשים ב-GestureDetector עם מאזינים שונים. גישה זו מבטלת טריגרים שגויים במהלך עבודה אינטנסיבית.
רינדור קצוות (חיצים)
עקומות בזייה לחיבורים בין צמתים נראות טוב יותר מקווים ישרים. עקומה קובית מידית פלט לידית קלט:
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כל קצה הוא 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.path לעדכונים מיידיים. כאשר יש קצוות רבים (50+), רינדור אותם דרך CATransaction.begin(); CATransaction.setDisableActions(true) יחיד עם מתודת ציור: CALayer אחד לכל הקצוות מהיר יותר מ-50 שכבות נפרדות.
יצירת קצוות אינטראקטיבית
המשתמש גורר מידית הפלט של צומת אחד לידית הקלט של אחר כדי ליצור קצה חדש. יישום:
-
CGContext.addPath()(משך 0.0) על תצוגת הידית — התחל גרירה - ב-
UILongPressGestureRecognizer, צור.began, נקודת התחלה קבועה על הידית - ב-
temporaryEdgeLayer, עדכן את סוף.changedלמיקום האצבע - בדיקת פגיעה על כל
temporaryEdgeLayer:.changed— אם פגענו בידית קלט, הדגש אותה - ב-
canvas.hitTest(location, with: nil), אם מתחת לאצבע יש ידית קלט, צור קצה קבוע; אחרת הסר את השכבה הזמנית
מנע חיבורים לא חוקיים: לא ניתן לחבר צומת לעצמו, לא ניתן ליצור קצוות כפולים, לא ניתן ליצור מעגלים (אם הגרף חייב להיות DAG). בדיקת מעגלים: DFS מהצומת היעד. O(V + E) — מהיר עבור עצי דיאלוג טיפוסיים.
עריכת צמתים
טאפ על צומת פותח עורך מובנה. עבור צומת דיאלוג מסוג "הודעה": .ended עם תמיכה בטקסט עשיר לתוכן ההודעה, מתג סוג (טקסט/מדיה/כפתורים), ורשימת כפתורים עם יכולת הוספה/הסרה.
Bottom sheet (iOS 15+ UITextView עם UISheetPresentationController ו-.medium detents) — המשתמש רואה גם את הגרף וגם את העורך במצב בינוני. מניעת מקלדת: כאשר המקלדת מופיעה, הגדל את הריפוד התחתון של .large בתוך ה-sheet ב-UIScrollView. טפל באמצעות keyboardFrame.height - sheet.frame.height + sheetMaxHeight.
ביטול/ביצוע מחדש
תבנית Command — כל פעולה (הזזת צומת, יצירה/מחיקת קצה, עריכת תוכן) מיושמת כאובייקט פקודה עם שיטות UIKeyboardWillShowNotification ו-execute().
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
}
}undo() — מובנה ב-iOS UIKit, עובד עם 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 מותאם אישית עם registerUndo(withTarget:handler:) ו-CommandStack. קיבוץ: הזזת מספר צמתים נבחרים היא פעולת ביטול אחת.
בחירה ובחירה מרובה
בחירת לאסו: pan על שטח ריק תוך לחיצה ממושכת — מצייר מלבן בחירה באמצעות undoStack: [GraphCommand] עם redoStack: [GraphCommand] ו-CAShapeLayer. בסיום המחווה, בדוק את כל הצמתים לחיתוך. צמתים נבחרים מקבלים גבול כחול באמצעות fillColor = UIColor.blue.withAlphaComponent(0.1). Pan על הבחירה מזיז את כל הצמתים הנבחרים בו-זמנית. מחיקה מסירה את כל הצמתים הנבחרים ואת הקצוות המחוברים.
סריאליזציה וסנכרון
גרף → JSON עם תיאור מלא של צמתים וקצוות. שמירה אוטומטית כל 30 שניות או בכל שינוי עם debounce של 2 שניות. ב-iOS, השתמש ב-strokeColor = UIColor.blue על publisher של שינוי גרף, ולאחר מכן layer.borderColor כדי לשלוח לשרת.
פתרון קונפליקטים לעריכה שיתופית: Last-Write-Wins ברמת הצומת עם חותמת זמן של השרת. לכל צומת יש Combine.debounce(for: .seconds(2), scheduler: RunLoop.main) — במהלך מיזוג, קח את הגרסה עם חותמת הזמן המאוחרת יותר.
מה כלול בעבודה
- מחקר ועיצוב מבנה הצמתים עבור הצ'אטבוט שלך
- יישום קנבס מותאם אישית עם גלילה אינסופית וזום
- מערכת ביטול/ביצוע מחדש ובחירה מרובה
- אינטגרציה עם הקצה האחורי שלך באמצעות REST/GraphQL
- תמיכה בכל סוגי הצמתים (טקסט, מדיה, כפתורים, תנאים)
- בדיקות על מכשירי יעד ופרסום לחנויות
- תיעוד והדרכה לצוות שלך
ציר זמן: משבוע עד 3 חודשים. התמחור נקבע באופן אישי לאחר ניתוח הדרישות שלך. עלויות הפיתוח מתחילות מ-$5,000 עבור גרסה בסיסית מבוססת אינטרנט ויכולות להגיע עד $50,000 עבור פתרון טבעי מלא עם תכונות מתקדמות. צור קשר — נעריך את הפרויקט שלך ונציע את הפתרון האופטימלי.







