Skip to content

codelynx/Panorama

Repository files navigation

Panorama

Swift Platforms License SPM Compatible

Panorama is a Swift framework for building Core Graphics-based 2D scrollable and zoomable applications that work seamlessly on both iOS and macOS. It abstracts away the platform differences between UIKit and AppKit, providing a unified API for creating high-performance graphics applications.

โœจ Features

  • ๐ŸŽฏ Cross-Platform: Single codebase for iOS and macOS
  • ๐Ÿš€ High Performance: Direct Core Graphics rendering
  • ๐Ÿ” Zooming & Panning: Built-in support for smooth interactions
  • ๐Ÿ“ Coordinate System Management: Handles platform differences automatically
  • ๐ŸŽจ Viewlet System: Lightweight custom drawing components
  • ๐Ÿ“ฑ Touch & Mouse Support: Unified event handling with improved stability
  • ๐Ÿงฉ Extensible: Easy to create custom viewlets
  • ๐Ÿ›ก๏ธ Type Safe: Modern Swift 5.9+ with improved type safety
  • โŒจ๏ธ Text Input: Built-in TextFieldViewlet for keyboard input
  • ๐ŸŽด Draggable Cards: NoteCardViewlet for interactive card interfaces

๐Ÿ†• What's New in v1.0.0

Bug Fixes

  • Fixed infinite recursion in hit testing (findViewlet method)
  • Fixed touch handling recursion for nested Panoramas
  • Fixed touch location calculation to prevent stack overflow
  • Fixed upside-down rendering issue on iOS platforms

New Components

  • TextFieldViewlet: Full-featured text input with keyboard support
  • FormExampleViewlet: Example implementation of form layouts
  • NoteCardViewlet: Draggable card components with delete functionality

๐Ÿ“‹ Requirements

  • Swift 5.9+
  • iOS 13.0+ / macOS 10.13+
  • Xcode 15.0+

๐Ÿ“ฆ Installation

Swift Package Manager

Add Panorama to your project by adding the following to your Package.swift:

dependencies: [
    .package(url: "https://github.com/codelynx/Panorama.git", from: "1.0.0")
]

Or in Xcode:

  1. File โ†’ Add Package Dependencies
  2. Enter the repository URL
  3. Select version 1.0.0 or later

๐Ÿš€ Quick Start

Basic Setup

  1. Create a subclass of Panorama:
import Panorama

class MyPanorama: Panorama {
    override func draw(in context: CGContext) {
        // Your Core Graphics drawing code here
        context.setFillColor(XColor.blue.cgColor)
        context.fill(CGRect(x: 100, y: 100, width: 200, height: 200))
    }
    
    override func didMove(to panoramaView: PanoramaView?) {
        super.didMove(to: panoramaView)
        // Setup code when panorama is attached to a view
    }
}
  1. Set up the PanoramaView in your view controller:
class ViewController: XViewController {
    @IBOutlet weak var panoramaView: PanoramaView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Create and configure your panorama
        let myPanorama = MyPanorama(frame: CGRect(x: 0, y: 0, width: 2048, height: 2048))
        myPanorama.minimumZoomScale = 0.5
        myPanorama.maximumZoomScale = 4.0
        
        // Attach to the view
        panoramaView.panorama = myPanorama
    }
}

Event Handling

Handle platform-specific events with unified methods:

class InteractivePanorama: Panorama {
    #if os(iOS)
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        let location = touch.location(in: self)
        // Handle touch
    }
    #endif
    
    #if os(macOS)
    override func mouseDown(with event: NSEvent) {
        let location = event.location(in: self)
        // Handle mouse click
    }
    #endif
}

๐Ÿ—๏ธ Architecture

Core Components

Panorama

The main content container that manages your drawable scene. It's not a UIView/NSView subclass but provides view-like functionality with platform-agnostic APIs.

PanoramaView

A UIView/NSView subclass that hosts the panorama content and manages scrolling/zooming behavior. It automatically creates the necessary scroll view hierarchy.

Viewlet System

Lightweight drawable components that can be composed hierarchically:

class CustomViewlet: Viewlet {
    override func draw(in context: CGContext) {
        // Custom drawing code
    }
    
    override func singleAction() {
        // Handle single tap/click
    }
}

Architecture Diagram

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  PanoramaView   โ”‚ (UIView/NSView)
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚  BackView   โ”‚ โ”‚ โ† Renders content
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ ScrollView  โ”‚ โ”‚ โ† Handles scrolling
โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚
โ”‚ โ”‚ โ”‚Content  โ”‚ โ”‚ โ”‚ โ† Captures events
โ”‚ โ”‚ โ”‚  View   โ”‚ โ”‚ โ”‚
โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
         โ†“
    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
    โ”‚Panorama โ”‚ โ† Your content
    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ“– Advanced Usage

Text Input with TextFieldViewlet

let textField = TextFieldViewlet(frame: CGRect(x: 50, y: 100, width: 200, height: 30))
textField.placeholder = "Enter your name"
textField.text = ""
textField.textColor = .black
textField.backgroundColor = .white
textField.cornerRadius = 5

// Handle text changes
textField.onTextChange = { newText in
    print("Text changed: \(newText)")
}

// Handle return key
textField.onReturn = {
    print("Return key pressed")
    textField.resignFocus()
}

panorama.addViewlet(textField)

Draggable Cards with NoteCardViewlet

let noteCard = NoteCardViewlet(frame: CGRect(x: 100, y: 100, width: 200, height: 150))
noteCard.text = "Drag me around!"
noteCard.onDelete = {
    panorama.removeViewlet(noteCard)
}
panorama.addViewlet(noteCard)

Custom Viewlets

Create reusable drawing components:

class ButtonViewlet: Viewlet {
    var title: String = "Button"
    var isHighlighted = false
    
    override func draw(in context: CGContext) {
        // Draw background
        let fillColor = isHighlighted ? XColor.blue : XColor.gray
        context.setFillColor(fillColor.cgColor)
        context.fillEllipse(in: bounds)
        
        // Draw text
        let attributes: [NSAttributedString.Key: Any] = [
            .font: XFont.systemFont(ofSize: 16),
            .foregroundColor: XColor.white
        ]
        let string = NSAttributedString(string: title, attributes: attributes)
        drawText(in: context, rect: bounds, attributedString: string, verticalAlignment: .center)
    }
    
    override func singleAction() {
        print("Button tapped: \(title)")
    }
}

Styling System

Apply consistent styling across viewlets:

let style = ViewletStyle()
style.cornerRadius = 8.0
style.font = XFont.boldSystemFont(ofSize: 14)
style.setForegroundColor(.white, for: .normal)
style.setForegroundColor(.gray, for: .highlighted)
style.setBackgroundFill(.linearGradient(direction: .vertical, colors: [.blue, .purple]), for: .normal)

Cross-Platform Type Aliases

Panorama provides unified type aliases for platform-specific types:

Alias iOS macOS
XView UIView NSView
XViewController UIViewController NSViewController
XColor UIColor NSColor
XImage UIImage NSImage
XFont UIFont NSFont
XBezierPath UIBezierPath NSBezierPath
XScrollView UIScrollView NSScrollView

๐ŸŽฏ Use Cases

Panorama is perfect for:

  • ๐ŸŽจ Drawing and sketching applications
  • ๐Ÿ“Š Diagramming and flowchart tools
  • ๐Ÿ—บ๏ธ Map viewers and floor plan applications
  • ๐Ÿ“ Technical drawing and CAD-like applications
  • ๐Ÿ–ผ๏ธ Image viewers with annotation support
  • ๐ŸŽฎ 2D games and interactive visualizations

๐Ÿค Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE.md file for details.

๐Ÿ™ Acknowledgments

  • Original framework by Kaz Yoshikawa
  • Modernized to Swift 5.9+ with community contributions
  • Uses XPlatform for additional cross-platform utilities

๐Ÿ“š Documentation

For detailed API documentation, see API Reference.

For migration from version 1.x, see Migration Guide.

๐Ÿ’ฌ Support

About

iOS/macOS 2D scrollable/zoomable utility code and it's sample code

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •