Skip to content

Commit c9110b8

Browse files
committed
chore: cleanup native elements docs
1 parent 232c051 commit c9110b8

File tree

2 files changed

+309
-136
lines changed

2 files changed

+309
-136
lines changed

content/guide/create-custom-native-elements.md

Lines changed: 252 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,20 @@ contributors:
55
- NathanWalker
66
---
77

8-
When working on view markup with NativeScript, a collection of elements you interact with are registered for you like [GridLayout](https://docs.nativescript.org/ui/grid-layout), [Button](https://docs.nativescript.org/ui/button), [Label](https://docs.nativescript.org/ui/label), etc. These are just commonly used elements.
8+
When working with view markup in NativeScript, you interact with a set of pre-registered elements, such as [`GridLayout`](https://docs.nativescript.org/ui/grid-layout), [`Button`](https://docs.nativescript.org/ui/button), and [`Label`](https://docs.nativescript.org/ui/label). These are commonly referred to as **"view components."**
99

10-
You can create new custom native elements or extend existing ones to enhance behavior for all sorts of cases.
10+
You can also create custom native elements or extend existing ones to add specialized functionality to your application.
1111

12-
Let's first look at how you would register new elements across all flavors and then we'll discuss how to build one, starting with a simple example of a custom View class.
12+
This guide demonstrates how to:
13+
14+
- Register custom view elements across all NativeScript flavors.
15+
- Implement a simple custom view component, starting from a basic class definition:
1316

1417
```ts
1518
import { View } from '@nativescript/core'
1619

1720
export class Checkbox extends View {
18-
// impl
21+
// implementation details
1922
}
2023
```
2124

@@ -138,109 +141,303 @@ You can now use it like anything else:
138141
</Tab>
139142
</Tabs>
140143

141-
## Creating New Views
144+
## Creating Custom View Components
142145

143-
Creating a new view element for use across anything in NativeScript (Angular, React, Solid, Svelte, TypeScript by itself, Vue, etc) is exactly the same.
146+
The process for creating new NativeScript view components is consistent across Angular, React, Solid, Svelte, TypeScript, and Vue.
144147

145-
### Custom View Anatomy
148+
### Anatomy of a Custom View
146149

147-
There's only 2 **fundamental** required aspects to any custom NativeScript view component (with 2 _optional_ additions):
150+
Every custom NativeScript view must have:
148151

149-
1. Extend any NativeScript View.
150-
2. (**required**) `createNativeView`: Construct and return any platform native view.
151-
3. (_optional_) `initNativeView`: Initialize anything.
152-
4. (_optional_) `disposeNativeView`: Cleanup anything.
152+
- (**required**) A class extending any NativeScript View.
153+
- (**required**) `createNativeView`: Construct and return a platform-native view. _Note: It's only required if you want to override what's being returned from the extended class._
154+
- (_optional_) `initNativeView`: Perform initialization after creation.
155+
- (_optional_) `disposeNativeView`: Cleanup resources when removed.
153156

154-
Let's look at those within the context of an example:
157+
Example:
155158

156159
```ts
157-
// 1. (required) Extend any NativeScript View
160+
import { View } from '@nativescript/core'
161+
158162
export class CustomView extends View {
159-
// 2. (required) Construct and return any platform native view
160163
createNativeView() {
161-
// return instance of UIView or android.view.View;
164+
// return UIView or android.view.View instance
162165
}
163166

164-
// 3. (optional) initialize anything
165-
initNativeView() {}
167+
initNativeView() {
168+
// initialization code
169+
}
166170

167-
// 4. (optional) cleanup anything
168-
disposeNativeView() {}
171+
disposeNativeView() {
172+
// cleanup code
173+
}
169174
}
170175
```
171176

172-
Let's explain each point respectively.
177+
#### Extending Existing Views
173178

174-
1. (required) extend any NativeScript View: **any** NativeScript view, for example these are all valid:
179+
You can extend **any** existing NativeScript view, for example:
175180

176181
```ts
177182
import { ContentView, Label, Button } from '@nativescript/core'
178183

179-
export class CustomView1 extends ContentView {
180-
// impl
181-
}
182-
183-
export class CustomView2 extends Label {
184-
// impl
185-
}
186-
187-
export class CustomView3 extends Button {
188-
// impl
189-
}
184+
export class CustomView1 extends ContentView {}
185+
export class CustomView2 extends Label {}
186+
export class CustomView3 extends Button {}
190187
```
191188

192-
2. `createNativeView`: Quite literally create and return **any** platform native view, for example these are both valid:
189+
#### `createNativeView` Examples
190+
191+
This method must return an instance of the native view:
193192

194193
```ts
195194
// iOS
196195
createNativeView() {
197-
return new WKWebView({
198-
frame: CGRectZero,
199-
configuration: configuration,
200-
});
196+
return new WKWebView({ frame: CGRectZero, configuration });
201197
}
198+
202199
// Android
203200
createNativeView() {
204201
return new android.webkit.WebView(this._context);
205202
}
206203
```
207204

208-
3. `initNativeView`: Initialize anything you'd like.
209-
210-
4. `disposeNativeView`: Destroy and cleanup anything if needed.
211-
212-
You will find many examples of this pattern throughout @nativescript/core, for example:
205+
#### Initialization and Cleanup
213206

214-
- [Button implementation for iOS](https://github.com/NativeScript/NativeScript/blob/96af6fa83e586a1c443c8b179701450d803e12aa/packages/core/ui/button/index.ios.ts#L21)
215-
- [Button implementation for Android](https://github.com/NativeScript/NativeScript/blob/96af6fa83e586a1c443c8b179701450d803e12aa/packages/core/ui/button/index.android.ts#L72)
207+
- `initNativeView`: Perform setup after the view has been created.
208+
- `disposeNativeView`: Free resources as needed.
216209

217-
All NativeScript plugins follow this exact same pattern as well, for example:
210+
Explore more examples within NativeScript core and plugins:
218211

219-
- [@nativescript/flutter for iOS](https://github.com/NativeScript/ui-kit/blob/dca883ada479a6d0982abbcb01a51f661d927812/packages/flutter/index.ios.ts#L40)
220-
- [@nativescript/flutter for Android](https://github.com/NativeScript/ui-kit/blob/dca883ada479a6d0982abbcb01a51f661d927812/packages/flutter/index.android.ts#L80)
212+
- [Button implementation (iOS)](https://github.com/NativeScript/NativeScript/blob/96af6fa83e586a1c443c8b179701450d803e12aa/packages/core/ui/button/index.ios.ts#L21)
213+
- [Button implementation (Android)](https://github.com/NativeScript/NativeScript/blob/96af6fa83e586a1c443c8b179701450d803e12aa/packages/core/ui/button/index.android.ts#L72)
214+
- [Custom Flutter view example (iOS)](https://github.com/NativeScript/ui-kit/blob/dca883ada479a6d0982abbcb01a51f661d927812/packages/flutter/index.ios.ts#L40)
215+
- [Custom Flutter view example (Android)](https://github.com/NativeScript/ui-kit/blob/dca883ada479a6d0982abbcb01a51f661d927812/packages/flutter/index.android.ts#L80)
221216

222-
You can also learn a lot about this with additional details such as using Cocoapods, Gradle and more in [this blog post series](https://blog.nativescript.org/create-a-custom-view-plugin-marquee-label/).
217+
For additional details on advanced topics like using Cocoapods or Gradle, refer to [this blog series](https://blog.nativescript.org/create-a-custom-view-plugin-marquee-label/).
223218

224-
### Implementing within a Project
219+
## Project Structure for Custom Views
225220

226-
Implementing custom native elements at any moment within a project starts with creating a folder for your new element followed by the implementation. A common folder organization is as follows:
221+
Create a dedicated folder for your component, such as:
227222

228223
```bash
229224
./checkbox
230225
common.ts
231226
index.android.ts
232-
index.d.ts
233227
index.ios.ts
228+
index.d.ts
234229
```
235230

236-
This represents a new encapsulated custom native `<Checkbox />` element sharing common code via `common.ts` with each platform implementation represented via it's suffix. The `index.d.ts` is a convenience allowing one to simply import via the folder to register the element anywhere, for example:
231+
This structure encapsulates platform-specific implementations and shared logic, simplifying imports and registration:
237232

238233
```ts
239234
import { Checkbox } from './checkbox'
235+
```
236+
237+
## Real-World Example: Custom Checkbox
238+
239+
Let's create a `<Checkbox>` component that behaves consistently on iOS and Android like this:
240+
241+
<iframe style="width: 100%; min-height: 200px; aspect-ratio: 16 / 9;" src="https://www.youtube.com/embed/08uip43irOM" title="Creating custom elements with NativeScript" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
242+
243+
See the full working example on StackBlitz: https://stackblitz.com/edit/nativescript-create-custom-elements-checkbox?file=src%2Fapp%2Fcheckbox%2Fcommon.ts
240244

241-
// register custom element using examples above for the flavor you're using
245+
In NativeScript, creating custom UI components can be straightforward and powerful. In this guide, you'll learn how to build a simple, reusable Checkbox component using only built-in modules from `@nativescript/core`. We'll leverage a combination of `GridLayout`, `Label`, and Material Design icons.
246+
247+
### Step-by-Step Guide
248+
249+
#### 1. Extend `GridLayout`
250+
251+
The base structure of our Checkbox will utilize `GridLayout`, which serves as a container for the checkbox icon:
252+
253+
```typescript
254+
import { GridLayout } from '@nativescript/core'
255+
256+
export class CheckboxCommon extends GridLayout {}
257+
```
258+
259+
#### 2. Initialize Checkbox with a Font Icon
260+
261+
Next, we'll add a Label to serve as our checkbox icon, using Material Design icons for the visual representation.
262+
263+
```typescript
264+
import { GridLayout, Label, Color } from '@nativescript/core'
265+
266+
export class CheckboxCommon extends GridLayout {
267+
checked = false
268+
defaultColor = new Color('#dddddd')
269+
selectionColor = new Color('#4CAF50')
270+
271+
private _iconLabel: Label
272+
private _circleIcon = String.fromCharCode(0xf764)
273+
private _checkmarkIcon = String.fromCharCode(0xf5e0)
274+
275+
constructor() {
276+
super()
277+
278+
this.horizontalAlignment = 'center'
279+
this.verticalAlignment = 'middle'
280+
281+
this._iconLabel = new Label()
282+
this._iconLabel.text = this._circleIcon
283+
this._iconLabel.className = 'mat'
284+
this._iconLabel.color = this.defaultColor
285+
this._iconLabel.horizontalAlignment = 'center'
286+
this._iconLabel.verticalAlignment = 'middle'
287+
288+
this.addChild(this._iconLabel)
289+
}
290+
}
242291
```
243292

293+
Include Material Design Icons in your project by adding this CSS class in your `app.css`:
294+
295+
```css
296+
.mat {
297+
font-family: 'Material Design Icons', 'MaterialDesignIcons';
298+
font-weight: 400;
299+
}
300+
```
301+
302+
Ensure you've added the MaterialDesignIcons font file to your app's `fonts` directory.
303+
304+
---
305+
306+
#### 3. Implementing Animation
307+
308+
To enhance user interaction, add a simple scaling animation when toggling the checkbox state:
309+
310+
```typescript
311+
import { CoreTypes } from '@nativescript/core'
312+
313+
export class CheckboxCommon extends GridLayout {
314+
// existing properties and constructor...
315+
316+
private _animateCheckmark() {
317+
if (!this.checked) {
318+
this._iconLabel.color = this.defaultColor
319+
}
320+
321+
this._iconLabel
322+
.animate({
323+
scale: { x: 0.5, y: 0.5 },
324+
duration: 200,
325+
curve: CoreTypes.AnimationCurve.easeInOut,
326+
})
327+
.then(() => {
328+
this._iconLabel.text = this.checked
329+
? this._checkmarkIcon
330+
: this._circleIcon
331+
332+
if (this.checked) {
333+
this._iconLabel.color = this.selectionColor
334+
}
335+
336+
this._iconLabel.animate({
337+
scale: { x: 1, y: 1 },
338+
duration: 200,
339+
curve: CoreTypes.AnimationCurve.easeInOut,
340+
})
341+
})
342+
}
343+
}
344+
```
345+
346+
---
347+
348+
#### 4. Defining Customizable Properties
349+
350+
NativeScript's `Property` system allows you to easily expose customizable attributes:
351+
352+
```typescript
353+
const sizeProperty = new Property<CheckboxCommon, number>({
354+
name: 'size',
355+
defaultValue: 24,
356+
affectsLayout: __APPLE__,
357+
})
358+
359+
const checkedProperty = new Property<CheckboxCommon, boolean>({
360+
name: 'checked',
361+
defaultValue: false,
362+
valueConverter: booleanConverter,
363+
})
364+
365+
const defaultColorProperty = new Property<CheckboxCommon, Color>({
366+
name: 'defaultColor',
367+
equalityComparer: Color.equals,
368+
valueConverter: (v) => new Color(v),
369+
})
370+
371+
const selectionColorProperty = new Property<CheckboxCommon, Color>({
372+
name: 'selectionColor',
373+
equalityComparer: Color.equals,
374+
valueConverter: (v) => new Color(v),
375+
})
376+
```
377+
378+
These properties automatically handle conversions, such as strings to colors or booleans.
379+
380+
---
381+
382+
#### 5. Connecting Properties to Native Views
383+
384+
Link these properties to your checkbox component:
385+
386+
```typescript
387+
export class CheckboxCommon extends GridLayout {
388+
// existing implementation...
389+
390+
[sizeProperty.setNative](value: number) {
391+
this._iconLabel.fontSize = value
392+
}
393+
394+
[checkedProperty.setNative](value: boolean) {
395+
this.checked = value
396+
if (this.isLoaded) {
397+
this._animateCheckmark()
398+
}
399+
}
400+
401+
[defaultColorProperty.setNative](value: Color) {
402+
this.defaultColor = value
403+
if (this._iconLabel) {
404+
this._iconLabel.color = this.defaultColor
405+
}
406+
}
407+
408+
[selectionColorProperty.setNative](value: Color) {
409+
this.selectionColor = value
410+
}
411+
}
412+
413+
// Register properties
414+
sizeProperty.register(CheckboxCommon)
415+
checkedProperty.register(CheckboxCommon)
416+
defaultColorProperty.register(CheckboxCommon)
417+
selectionColorProperty.register(CheckboxCommon)
418+
```
419+
420+
---
421+
422+
### Complete Example and Testing
423+
424+
Explore and interact with the complete example directly:
425+
426+
- **Full Implementation:** [Common Checkbox Component](https://stackblitz.com/edit/nativescript-create-custom-elements-checkbox?file=src%2Fapp%2Fcheckbox%2Fcommon.ts)
427+
- **Element Registration:** [Checkbox Registration](https://stackblitz.com/edit/nativescript-create-custom-elements-checkbox?file=src%2Fapp%2Fapp.component.ts%3AL5)
428+
429+
---
430+
431+
### Summary
432+
433+
Creating custom components in NativeScript allows you to:
434+
435+
- Build complex components from simple elements
436+
- Utilize powerful styling and animations
437+
- Easily manage and expose customizable properties
438+
439+
Whether creating fully native views or hybrid custom components, NativeScript provides flexibility and efficiency to meet your application's needs.
440+
244441
## Customize Existing Elements?
245442

246-
Rather than create a new element from scratch, you may want to customize existing view elements. Learn about how to [extend any element for custom beavhior here](/guide/customizing-view-elements).
443+
Rather than create new elements from scratch, you may want to just adjust existing view elements. Learn about how to [extend any element for custom behavior here](/guide/customizing-view-elements).

0 commit comments

Comments
 (0)