In this lesson, you'll learn how to create custom native UI components using the Expo Modules API. Native views provide better performance and access to platform-specific UI elements that aren't available in React Native.

What are Native Views?

Native views are custom UI components written in native code (Swift/Kotlin) that can be used like any other React Native component. They're useful for:

  • Integrating native UI libraries
  • Creating performant custom components
  • Accessing platform-specific UI elements
  • Building complex animations and gestures

Creating a Native View

Let's create a custom view that displays a native label with platform-specific styling.

iOS Implementation (Swift)

import ExpoModulesCore
import UIKit
 
public class MyCustomView: ExpoView {
  let label = UILabel()
 
  required init(appContext: AppContext? = nil) {
    super.init(appContext: appContext)
    setupView()
  }
 
  func setupView() {
    addSubview(label)
    label.textAlignment = .center
    label.translatesAutoresizingMaskIntoConstraints = false
 
    NSLayoutConstraint.activate([
      label.centerXAnchor.constraint(equalTo: centerXAnchor),
      label.centerYAnchor.constraint(equalTo: centerYAnchor),
    ])
  }
}

Android Implementation (Kotlin)

package expo.modules.mymodule
 
import android.content.Context
import android.widget.TextView
import android.view.Gravity
import expo.modules.kotlin.views.ExpoView
 
class MyCustomView(context: Context) : ExpoView(context) {
  private val textView = TextView(context).apply {
    gravity = Gravity.CENTER
  }
 
  init {
    addView(textView)
  }
}

Module Definition

Define your view in the module:

// iOS
public class MyModule: Module {
  public func definition() -> ModuleDefinition {
    Name("MyModule")
 
    View(MyCustomView.self) {
      Prop("text") { (view: MyCustomView, text: String) in
        view.label.text = text
      }
 
      Events("onPress")
    }
  }
}
// Android
class MyModule : Module() {
  override fun definition() = ModuleDefinition {
    Name("MyModule")
 
    View(MyCustomView::class) {
      Prop("text") { view: MyCustomView, text: String ->
        view.setText(text)
      }
 
      Events("onPress")
    }
  }
}

Using Your Native View

In your React Native code:

import { requireNativeViewManager } from "expo-modules-core";
import { View } from "react-native";
 
const NativeView = requireNativeViewManager("MyModule");
 
export function MyCustomView({ text, onPress }) {
  return (
    <NativeView
      style={{ width: 200, height: 100 }}
      text={text}
      onPress={onPress}
    />
  );
}

Props and Events

Defining Props

Props allow you to pass data from JavaScript to native code:

Prop("text") { (view: MyCustomView, text: String) in
  view.label.text = text
}
 
Prop("backgroundColor") { (view: MyCustomView, color: UIColor) in
  view.backgroundColor = color
}

Handling Events

Events allow native code to communicate back to JavaScript:

Events("onPress", "onLongPress")
 
// Trigger event from native code
view.onPress?([:])

Best Practices

  • Keep views focused and single-purpose
  • Handle layout constraints properly
  • Clean up resources in view lifecycle methods
  • Use events for user interactions
  • Provide TypeScript definitions for better DX

Resources

Is this lesson useful?