Skip to content

androidZzT/oc-preview

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OCPreview

English | 中文

Real-time Objective-C UI preview for iOS — like Android Compose Preview, powered by InjectionIII.

Annotate any UIViewController with // @Preview, run in the Simulator, make a code change and save — the preview window updates without rebuilding.

Save .m file
  → FSEventStream detects change  (debounced 400 ms)
  → InjectionIII recompiles & dlopen-injects the new implementation
  → INJECTION_BUNDLE_NOTIFICATION fires
  → OCPreviewManager reloads affected OCPreviewWindow(s)
  → NSClassFromString re-instantiates the VC → rendered live

Requirements

Item Version
Xcode 14+
iOS Simulator 14+
InjectionIII latest (Mac App Store or GitHub)
CocoaPods 1.12+ (optional)
XcodeGen 2.x (optional, for Example project)

OCPreview is debug-only. It never ships in production builds.


Installation

CocoaPods

# Podfile
pod 'OCPreview', :configurations => ['Debug']
pod install

Manual (drag & drop)

  1. Copy the OCPreview/ folder into your project.
  2. Add all .h/.m files to your Debug target only.
  3. Link CoreServices.framework (weak-link on iOS).

Setup

1. Configure InjectionIII

  • Install InjectionIII from the Mac App Store or GitHub.
  • Launch InjectionIII and open your project directory.
  • InjectionIII watches for file saves and compiles changes automatically.

2. Start OCPreview in AppDelegate

// AppDelegate.m
#ifdef DEBUG
#import <OCPreview/OCPreview.h>
#endif

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

#ifdef DEBUG
    // Optional: customise before starting
    OCPreviewConfig *config = [OCPreviewConfig sharedConfig];
    config.position        = OCPreviewPositionRight;  // Right | Bottom | Center
    config.previewSize     = CGSizeMake(390, 720);
    config.maxPreviewCount = 3;

    // SOURCE_ROOT = root of your source tree (add to GCC_PREPROCESSOR_DEFINITIONS)
    [[OCPreviewManager sharedManager] startWithSourceDirectory:@SOURCE_ROOT];
#endif
    return YES;
}

Add SOURCE_ROOT to your Debug build settings:

GCC_PREPROCESSOR_DEFINITIONS (Debug) += SOURCE_ROOT=\"$(SRCROOT)\"

Alternatively pass any absolute path: startWithSourceDirectory:@"/Users/me/MyApp/Sources".

3. Annotate ViewControllers

Place // @Preview on the line immediately before @implementation:

// MyViewController.m

// @Preview
@implementation MyViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = UIColor.systemBlueColor;
    // Edit here and ⌘S — preview updates instantly
}

@end

Multiple VCs in the same file are supported — annotate each one:

// @Preview
@implementation CardViewController
...

// @Preview
@implementation HeaderViewController
...

Preview Window

Control Action
Drag title bar Move window anywhere on screen
Drag ⌟ corner handle Resize window
↺ button Manually reload VC
✕ button Close preview

When a compilation error occurs the preview area shows the error inline in red instead of crashing. Fix the error, save — the preview restores automatically.


Configuration Reference

OCPreviewConfig *config = [OCPreviewConfig sharedConfig];

// Window position relative to main app content
config.position             = OCPreviewPositionRight;   // default

// Size of the VC preview area (title bar is added on top)
config.previewSize          = CGSizeMake(390, 720);     // default: 375×667

// Max simultaneously open windows
config.maxPreviewCount      = 3;                         // default

// Auto-reload when InjectionIII fires (set NO to use ↺ only)
config.autoReload           = YES;                       // default

// Background colour of the window chrome
config.containerBackgroundColor = UIColor.systemBackgroundColor;

// Master on/off switch (defaults to YES in DEBUG, NO in Release)
config.enabled              = YES;

Running the Example Project

# Clone
git clone https://github.com/your-org/oc-preview.git
cd oc-preview/Example

# Generate the Xcode project (requires XcodeGen)
brew install xcodegen
bash generate_project.sh

# Open
open OCPreviewExample.xcodeproj
# — or, if you ran pod install —
open OCPreviewExample.xcworkspace

Then:

  1. Select the OCPreviewExample scheme on an iPhone Simulator.
  2. Press ⌘R to build and run.
  3. Launch InjectionIII and point it at the oc-preview/ directory.
  4. A floating preview window appears for SampleViewController.
  5. Edit SampleViewController.m, press ⌘S — watch the preview update!

Architecture

OCPreviewManager (singleton)
├── FSEventStream  — watches sourceDirectory for .m file saves
│     └── debounce (400 ms) → flushPendingChanges
│           └── OCPreviewScanner.scanFile:  → extracts @Preview class names
│                 └── ensurePreviewWindowForClass:
│
├── InjectionIII bridge
│     ├── Loads iOSInjection.bundle at startup
│     ├── INJECTION_BUNDLE_NOTIFICATION  → reloadViewController (per injected class)
│     └── INJECTION_COMPILE_ERROR        → showCompilationError: (all windows)
│
└── previewWindows: { className → OCPreviewWindow }

OCPreviewWindow (UIWindow subclass)
├── Title bar  — drag to move, ↺ reload, ✕ close
├── _OCPreviewContainerVC  — proper parent VC for correct lifecycle callbacks
│     └── previewChild  — the @Preview-annotated VC
├── OCPreviewErrorView  — red overlay for compile errors
└── Resize handle (⌟)  — pan to resize, clamped to screen + min size
File Responsibility
OCPreviewManager.h/m FSEventStream, InjectionIII bridge, window lifecycle, thread safety
OCPreviewScanner.h/m Parses // @Preview annotations from .m source files
OCPreviewWindow.h/m Floating window: drag/resize, VC embedding, error overlay
OCPreviewErrorView.h/m Red error display with formatted compiler output
OCPreviewConfig.h/m All configurable settings, DEBUG-only guard

FAQ

Q: Preview doesn't update after I save.

  • Is InjectionIII running and connected to your project directory?
  • Check the Xcode console for [OCPreview] Injection succeeded or error messages.
  • Tap the button to force-reload without InjectionIII.

Q: "Class not found in runtime" error.

  • The class hasn't been compiled yet. Build once (⌘B) then save the file.
  • Check the class name in // @Preview matches @implementation exactly.

Q: Previews work in Simulator but not on device.

Q: I want to preview with custom init parameters.

  • Add a + (instancetype)previewInstance class method and override the loading:
// @Preview
@implementation MyViewController

+ (instancetype)previewInstance {
    return [[self alloc] initWithModel:[MyModel stub]];
}
...

Then in your AppDelegate or a subclass of OCPreviewManager, swap in this factory. (Full custom factory support coming in a future release.)


License

MIT — see LICENSE.

About

Real-time Objective-C UI preview for iOS, powered by InjectionIII — like Android Compose Preview

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors