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.
- ๐ฏ 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
- Fixed infinite recursion in hit testing (
findViewletmethod) - Fixed touch handling recursion for nested Panoramas
- Fixed touch location calculation to prevent stack overflow
- Fixed upside-down rendering issue on iOS platforms
- TextFieldViewlet: Full-featured text input with keyboard support
- FormExampleViewlet: Example implementation of form layouts
- NoteCardViewlet: Draggable card components with delete functionality
- Swift 5.9+
- iOS 13.0+ / macOS 10.13+
- Xcode 15.0+
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:
- File โ Add Package Dependencies
- Enter the repository URL
- Select version 1.0.0 or later
- 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
}
}- 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
}
}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
}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.
A UIView/NSView subclass that hosts the panorama content and manages scrolling/zooming behavior. It automatically creates the necessary scroll view hierarchy.
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
}
}โโโโโโโโโโโโโโโโโโโ
โ PanoramaView โ (UIView/NSView)
โโโโโโโโโโโโโโโโโโโค
โ โโโโโโโโโโโโโโโ โ
โ โ BackView โ โ โ Renders content
โ โโโโโโโโโโโโโโโ โ
โ โโโโโโโโโโโโโโโ โ
โ โ ScrollView โ โ โ Handles scrolling
โ โ โโโโโโโโโโโ โ โ
โ โ โContent โ โ โ โ Captures events
โ โ โ View โ โ โ
โ โ โโโโโโโโโโโ โ โ
โ โโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโ
โPanorama โ โ Your content
โโโโโโโโโโโ
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)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)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)")
}
}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)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 |
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
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.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE.md file for details.
- Original framework by Kaz Yoshikawa
- Modernized to Swift 5.9+ with community contributions
- Uses XPlatform for additional cross-platform utilities
For detailed API documentation, see API Reference.
For migration from version 1.x, see Migration Guide.
- ๐ Issues: GitHub Issues