Skip to main content

Overview

Paw & Care leverages Capacitor to bridge web technologies with native iOS capabilities. This provides a seamless mobile experience with access to device features like camera, haptics, status bar, keyboard, and more. Capacitor Version: 8.x
Platform: iOS 15.0+
Configuration: capacitor.config.ts

Installed Plugins

The app includes the following Capacitor plugins:

Core

@capacitor/core (8.1.0) - Base Capacitor runtime

iOS Platform

@capacitor/ios (8.1.0) - iOS platform support

Haptics

@capacitor/haptics (8.0.0) - Tactile feedback

Keyboard

@capacitor/keyboard (8.0.0) - Keyboard controls

Status Bar

@capacitor/status-bar (8.0.1) - Status bar styling

Splash Screen

@capacitor/splash-screen (8.0.1) - Launch screen
See package.json:18-24 for full dependency list.

Capacitor Configuration

The app’s native behavior is configured in capacitor.config.ts:
import type { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: 'com.pawandcare.vetassist',
  appName: 'Paw & Care',
  webDir: 'dist',
  server: {
    androidScheme: 'https',
  },
  plugins: {
    SplashScreen: {
      launchShowDuration: 2000,
      launchAutoHide: true,
      backgroundColor: '#ffffff',
      showSpinner: false,
      splashFullScreen: true,
      splashImmersive: true,
    },
    StatusBar: {
      style: 'LIGHT',
      backgroundColor: '#ffffff',
    },
    Keyboard: {
      resize: 'body',
      resizeOnFullScreen: true,
    },
  },
};

export default config;
Source: capacitor.config.ts:1-30

Capacitor Initialization

Native plugins are initialized when the app launches:
import { Capacitor } from '@capacitor/core';
import { StatusBar, Style } from '@capacitor/status-bar';
import { Keyboard } from '@capacitor/keyboard';
import { SplashScreen } from '@capacitor/splash-screen';

export async function initCapacitor() {
  // Only run on native platforms
  if (!Capacitor.isNativePlatform()) return;

  try {
    // Configure status bar
    await StatusBar.setStyle({ style: Style.Light });
    await StatusBar.setOverlaysWebView({ overlay: true });
  } catch {
    // Plugin might not be available
  }

  try {
    // Keyboard listeners
    Keyboard.addListener('keyboardWillShow', () => {
      document.body.classList.add('keyboard-open');
    });
    Keyboard.addListener('keyboardWillHide', () => {
      document.body.classList.remove('keyboard-open');
    });
  } catch {
    // Ignore errors
  }

  try {
    // Hide splash screen after app is ready
    await SplashScreen.hide();
  } catch {
    // Ignore
  }
}
Source: src/lib/capacitor.ts:10-39 Call this in your main app:
import { initCapacitor } from '@/lib/capacitor';

useEffect(() => {
  initCapacitor();
}, []);

Platform Detection

Check if Running Natively

import { Capacitor } from '@capacitor/core';

if (Capacitor.isNativePlatform()) {
  // Running in iOS or Android app
} else {
  // Running in web browser
}
Helper function from src/lib/capacitor.ts:70-72:
export function isNative(): boolean {
  return Capacitor.isNativePlatform();
}

Get Current Platform

import { getPlatform } from '@/lib/capacitor';

const platform = getPlatform();
// Returns: 'ios' | 'android' | 'web'

if (platform === 'ios') {
  // iOS-specific code
}
Implementation in src/lib/capacitor.ts:77-79.

Haptic Feedback

Provide tactile feedback for user interactions:

Light Tap

For navigation, tab switches, and subtle interactions:
import { hapticTap } from '@/lib/capacitor';

// In your component
const handleButtonPress = async () => {
  await hapticTap();
  // Continue with action
};
Usage in navigation:
const handleNavigate = useCallback((view: ViewType) => {
  hapticTap();
  setCurrentView(view);
}, []);
See src/App.tsx:104-108.

Medium Impact

For important actions, confirmations, and alerts:
import { hapticImpact } from '@/lib/capacitor';

// When saving a record
const saveRecord = async () => {
  await hapticImpact();
  await performSave();
};
Implementation:
export async function hapticTap() {
  if (!Capacitor.isNativePlatform()) return;
  try {
    const { Haptics, ImpactStyle } = await import('@capacitor/haptics');
    await Haptics.impact({ style: ImpactStyle.Light });
  } catch {
    // Ignore errors
  }
}

export async function hapticImpact() {
  if (!Capacitor.isNativePlatform()) return;
  try {
    const { Haptics, ImpactStyle } = await import('@capacitor/haptics');
    await Haptics.impact({ style: ImpactStyle.Medium });
  } catch {
    // Ignore errors
  }
}
Source: src/lib/capacitor.ts:44-65

Best Practices

Use Sparingly

Too much haptic feedback becomes annoying. Reserve for important interactions.

Match Intensity

Light taps for navigation, medium impacts for actions, heavy for critical alerts.

Test on Device

Haptics don’t work in simulator. Always test on physical iPhone.

Graceful Fallback

Always wrap haptic calls in try/catch. Not all devices support all feedback types.

Status Bar

Control the appearance of the iOS status bar:

Styling

import { StatusBar, Style } from '@capacitor/status-bar';

// Light status bar (white text, for dark backgrounds)
await StatusBar.setStyle({ style: Style.Light });

// Dark status bar (black text, for light backgrounds)
await StatusBar.setStyle({ style: Style.Dark });

Overlay WebView

Make the status bar transparent and overlay the web content:
await StatusBar.setOverlaysWebView({ overlay: true });
This allows the app to extend into the status bar area, creating an immersive experience.

Background Color

await StatusBar.setBackgroundColor({ color: '#ffffff' });
Configured in capacitor.config.ts:19-22:
StatusBar: {
  style: 'LIGHT',
  backgroundColor: '#ffffff',
}

Show/Hide

// Hide status bar (full screen mode)
await StatusBar.hide();

// Show status bar
await StatusBar.show();

Keyboard

Manage the on-screen keyboard behavior:

Keyboard Events

Listen for keyboard show/hide events:
import { Keyboard } from '@capacitor/keyboard';

Keyboard.addListener('keyboardWillShow', (info) => {
  console.log('Keyboard height:', info.keyboardHeight);
  document.body.classList.add('keyboard-open');
});

Keyboard.addListener('keyboardWillHide', () => {
  document.body.classList.remove('keyboard-open');
});
Implemented in src/lib/capacitor.ts:23-28.

Resize Behavior

Configure how the webview resizes when keyboard appears:
// In capacitor.config.ts
Keyboard: {
  resize: 'body',  // 'body' | 'ionic' | 'native' | 'none'
  resizeOnFullScreen: true,
}
Options:
  • body: Resize the body element (default)
  • ionic: Use Ionic Framework’s resize logic
  • native: Use native platform behavior
  • none: Don’t resize, keyboard overlays content

Manual Control

// Show keyboard
await Keyboard.show();

// Hide keyboard
await Keyboard.hide();

// Set accessory bar visibility (iOS toolbar above keyboard)
await Keyboard.setAccessoryBarVisible({ isVisible: true });

Scroll into View

Ensure input fields are visible when keyboard appears:
// Add to input fields
<input
  onFocus={(e) => {
    e.target.scrollIntoView({ behavior: 'smooth', block: 'center' });
  }}
/>

Splash Screen

Control the launch screen shown when app starts:

Configuration

// In capacitor.config.ts
SplashScreen: {
  launchShowDuration: 2000,      // Show for 2 seconds
  launchAutoHide: true,           // Auto-hide after duration
  backgroundColor: '#ffffff',     // Background color
  showSpinner: false,             // Hide loading spinner
  splashFullScreen: true,         // Full screen mode
  splashImmersive: true,          // Hide system UI
}

Manual Control

import { SplashScreen } from '@capacitor/splash-screen';

// Show splash screen
await SplashScreen.show({
  showDuration: 2000,
  autoHide: true,
});

// Hide splash screen
await SplashScreen.hide();
Hidden after app initialization:
await SplashScreen.hide();
See src/lib/capacitor.ts:34-37.

Custom Splash Screen

Replace default splash screen in Xcode:
1

Open Xcode project

npx cap open ios
2

Navigate to assets

In Xcode project navigator: App > App > Assets.xcassets > Splash
3

Add images

Drag your splash screen images (1x, 2x, 3x) into the asset catalog.
4

Configure LaunchScreen.storyboard

Adjust the storyboard to display your custom splash screen properly.

Additional Native Features

While not currently installed, Capacitor supports many other plugins:

Camera

npm install @capacitor/camera
npx cap sync
import { Camera, CameraResultType } from '@capacitor/camera';

const takePhoto = async () => {
  const image = await Camera.getPhoto({
    quality: 90,
    allowEditing: true,
    resultType: CameraResultType.Uri,
  });
  
  return image.webPath;
};

Filesystem

npm install @capacitor/filesystem
npx cap sync
import { Filesystem, Directory } from '@capacitor/filesystem';

const saveFile = async (data: string, filename: string) => {
  await Filesystem.writeFile({
    path: filename,
    data: data,
    directory: Directory.Documents,
  });
};

Geolocation

npm install @capacitor/geolocation
npx cap sync
import { Geolocation } from '@capacitor/geolocation';

const getCurrentPosition = async () => {
  const coordinates = await Geolocation.getCurrentPosition();
  console.log('Current position:', coordinates);
};

Local Notifications

npm install @capacitor/local-notifications
npx cap sync
import { LocalNotifications } from '@capacitor/local-notifications';

const scheduleNotification = async () => {
  await LocalNotifications.schedule({
    notifications: [{
      title: 'Appointment Reminder',
      body: 'Luna's checkup in 2 hours',
      id: 1,
      schedule: { at: new Date(Date.now() + 1000 * 60 * 60 * 2) },
    }],
  });
};

Push Notifications

npm install @capacitor/push-notifications
npx cap sync
import { PushNotifications } from '@capacitor/push-notifications';

const registerPush = async () => {
  await PushNotifications.requestPermissions();
  await PushNotifications.register();
  
  PushNotifications.addListener('registration', (token) => {
    console.log('Push token:', token.value);
  });
};

Building Custom Plugins

Create your own Capacitor plugin for custom native functionality:
1

Generate plugin

npm init @capacitor/plugin
2

Define TypeScript interface

export interface MyPluginPlugin {
  echo(options: { value: string }): Promise<{ value: string }>;
}
3

Implement iOS code

In Swift:
@objc func echo(_ call: CAPPluginCall) {
  let value = call.getString("value") ?? ""
  call.resolve(["value": value])
}
4

Use in your app

import { MyPlugin } from 'my-plugin';

const result = await MyPlugin.echo({ value: 'Hello' });
See Capacitor Plugin Guide for details.

Debugging Native Code

Xcode Console

View native logs:
  1. Open project in Xcode (npx cap open ios)
  2. Run the app (⌘R)
  3. Open Debug Area (⇧⌘Y)
  4. View Console tab for native logs

Safari Web Inspector

Debug web code running in the iOS app:
  1. Enable Develop menu in Safari Preferences
  2. Connect iPhone and run app
  3. Safari menu: Develop > [Your iPhone] > Paw & Care
  4. Web Inspector opens with full DevTools

Log Native Events

import { Capacitor } from '@capacitor/core';

if (Capacitor.isNativePlatform()) {
  console.log('Running on native platform:', Capacitor.getPlatform());
  console.log('App version:', Capacitor.convertFileSrc('version'));
}

Performance Tips

Lazy Import Plugins

Dynamically import heavy plugins to reduce initial bundle size.

Cache Plugin Instances

Reuse plugin instances instead of creating new ones for each call.

Minimize Bridge Calls

Batch operations when possible to reduce native ↔ web communication overhead.

Test on Real Devices

Simulator doesn’t accurately reflect real device performance.

Troubleshooting

Plugin Not Found

Error: Plugin [name] does not have web implementation Solution:
  • Ensure plugin is installed: npm install @capacitor/[plugin]
  • Sync native project: npx cap sync ios
  • Rebuild in Xcode

Permission Denied

Error: User denied permission Solution:
  • Add required permissions to Info.plist
    • Camera: NSCameraUsageDescription
    • Microphone: NSMicrophoneUsageDescription
    • Location: NSLocationWhenInUseUsageDescription
  • Delete and reinstall app to trigger permission prompts

Method Not Implemented

Error: Not implemented on [platform] Solution:
  • Check plugin documentation for platform support
  • Implement web fallback for unsupported platforms
  • Consider using different plugin or custom implementation

Next Steps

iOS App

Learn about iOS app architecture and deployment

Offline Mode

Understand offline capabilities and sync strategies

Dictation

Explore voice recording and SOAP generation features

Capacitor Docs

Official Capacitor documentation and plugin reference

Build docs developers (and LLMs) love