How to use a native SwiftUI View in NativeScript 7 - ios

In my NativeScript (Angular) App i use a RadListView to create a list and each element has many different informations to display. It looks like that
Because of many hints at Stackoverflow and other sources i reduced the amount of nested layouts (StackLayout, GridLayout, ...) as much as possible to make the RadListView faster. On Android is the performance by using the list much better as on iOS. With an iPad Pro (2020) the rendering of the list at scrolling is not smooth. If the user change the orientation of the device the screen is freezing and have black bars at the side or bottom for a moment. The time of the freezing depends on the amount of elements to display in each row. The same row layout in a ListView is much faster but not the same as native (SwiftUI) and with missing features like swipe and pull to refresh.
Sorry for the lyric but i think a little background explains why i try the next step.
To improve the user experience i make a tiny native test app with SwiftUI and nearly the same row layout. The feeling is much better, fast first loading, smooth scrolling and no delay by orientation changes. My next idea is to create a native component in SwiftUI to show/render each row of the RadListView if possible
<RadListView [items]="items">
<ListViewLinearLayout tkListViewLayout></ListViewLinearLayout>
<ng-template tkListItemTemplate let-item="item" let-i="index" let-odd="odd">
<MyNativeSwiftUIComponentElement data="item.rowData"></MyNativeSwiftUIComponentElement>
</ng-template>
</RadListView>
or use the List from SwiftUI to show/render the whole list
<ActionBar title="Objects"></ActionBar>
<MyNativeSwiftUIListComponent data="items"></MyNativeSwiftUIListComponent>
Looking for docs and examples was difficult. I found this very short advise Adding Objective-C/Swift code and the linked tutorial there for Objective-C (Adding Objective-C Code to a NativeScript App) and some questions on Stackoverflow but there all about classes and not SwiftUI (with struct and views). One question was about SwiftUI: Is it possible to display a View written with SwiftUI with NativeScript the answer was unfortunately not helpful for me (btw. thank you #Manoj for your great support for NativeScript at Stackoverflow!).
How can i use a SwiftUI View as native component in my {N}app?
Have anyone a hint, a link to a tutorial or a link to a public repository for a app/plugin? Every tiny tip is welcome.

You might be able to use Nativescript's placeholder component (more info on that here
So you would have the Placeholder tag on your template, and use the creatingView event to add the native UIs
<Placeholder creatingView="creatingView"/>
import { CreateViewEventData } from "#nativescript/core";
export function creatingView(args: CreateViewEventData) {
let nativeView = new UILabel(); // where this would be your native UI
nativeView.text = "Native";
args.view = nativeView;
}

After a while i give up with my attempts to use directly SwiftUI in the project ({N}+Angular) and instead i try the <Placeholder> component which #William-Juan suggested. But it looks like, that the <Placeholder> not official supported in the Angular flavor - see github issue #283
To move on, i looked at the samples for NativeScript plugins and build a working solution. If anybody interested the full sample source code are in this repository: https://github.com/teha-at/sample-nativescript-native-ui-component
First, create a class which extends the #nativescript/core/View class and has an item to get the data which will be to display.
// object-list-item.d.ts
// [...]
export class ObjectListItem extends View {
item: ObjectModel;
}
export const itemProperty: Property<ObjectListItem, string>;
Than create a abstract base class which also extends the #nativescript/core/View class and this creates the base for Android and iOS
// object-list-item.common.ts
// [...]
export const itemProperty = new Property<ObjectListItemBase, string>({
name: 'item',
defaultValue: null,
affectsLayout: isIOS,
});
export abstract class ObjectListItemBase extends View {
item: PortalObjectModel;
}
// defines 'item' property on the ObjectListItemBase class
itemProperty.register(ObjectListItemBase);
ObjectListItemBase.prototype.recycleNativeView = 'auto';
Because i was only looking for a component for iOS the object-list-item.android.ts are very simple:
// object-list-item.android.ts
import { ObjectListItemBase } from './object-list-item.common';
export class ObjectListItem extends ObjectListItemBase {}
For iOS there are much more lines, for the complete file content look at the github repo please.
/// object-list-item.ios.ts
// [...]
export class ObjectListItem extends ObjectListItemBase {
// added for TypeScript intellisense.
nativeView: UIView;
// [...]
/**
* Creates new native button.
*/
public createNativeView(): Object {
const mainUiStackView = UIStackView.new();
// [...]
}
/**
* Initializes properties/listeners of the native view.
*/
initNativeView(): void {
// Attach the owner to nativeView.
// When nativeView is tapped we get the owning JS object through this field.
(<any>this.nativeView).owner = this;
super.initNativeView();
}
/**
* Clean up references to the native view and resets nativeView to its original state.
* If you have changed nativeView in some other way except through setNative callbacks
* you have a chance here to revert it back to its original state
* so that it could be reused later.
*/
disposeNativeView(): void {
// Remove reference from native listener to this instance.
(<any>this.nativeView).owner = null;
// If you want to recycle nativeView and have modified the nativeView
// without using Property or CssProperty (e.g. outside our property system - 'setNative' callbacks)
// you have to reset it to its initial state here.
super.disposeNativeView();
}
[itemProperty.setNative](item: ObjectModel) {
this.item = item;
// [...]
}
}
Add an Angular directive
// object-list-item.directives.ts
#Directive({
selector: 'ObjectListItem',
})
export class ObjectListItemDirective {
}
export const ObjectListItemDirectives = [ObjectListItemDirective];
At least register the component in an Angular module.
// object-list-item.module.ts
// [...]
#NgModule({
imports: [],
declarations: [
ObjectListItemDirectives,
],
schemas: [NO_ERRORS_SCHEMA],
exports: [
ObjectListItemDirectives,
],
entryComponents: [],
})
export class ObjectListItemModule {
}
registerElement('ObjectListItem', () => ObjectListItem);
After all this steps call the new component in the template
<!-- [...] -->
<RadListView #myListView [items]="items$ | async">
<ng-template tkListItemTemplate let-item="item">
<StackLayout margin="0" padding="0" class="-separator m-y-5" height="90">
<android>
<!-- [...] -->
</android>
<ios>
<ObjectListItem [item]="item"></ObjectListItem>
</ios>
</StackLayout>
</ng-template>
</RadListView>
<!-- [...] -->
All this work is well spent. The UI is much faster and it feels more like a native app. At the mean time i build a prototype as a native iOS App in Swift and SwiftUI, of course this pure native app are a little bit more smoother, but at the moment i work with my {N}-App and the native component. Hope this sample will be useful for someone.

Related

How to use CDK overlay while leaving an existing component in the foreground?

The Angular Material CDK library provides various features including overlays. All the examples I can find show how to display a new component on top of the overlay. My goal is a little different. I want to display an existing component (one of several on the screen) on top of the overlay.
The behavior I have in mind is that when the user goes into a kind of editing mode on a particular object, the component representing that object would sort of "float" on top of an overlay, until editing is done or cancelled.
Is there any straightforward way to do that? It seems that the cdkConnectedOverlay directive might be useful, but I can't figure out how to make it work.
Angular CDK Provides you two ways to achieve that (Directives and Services).
Using the Overlay service you will need to call the create method passing the positionStrategy prop:
#Component({
....
})
class AppComponent {
#ViewChild('button') buttonRef: ElementREf;
...
ngOnInit() {
const overlayRef = overlay.create({
positionStrategy: getOverlayPosition(),
height: '400px',
width: '600px',
});
const userProfilePortal = new ComponentPortal(UserProfile);
overlayRef.attach(userProfilePortal);
}
getOverlayPosition(): PositionStrategy {
this.overlayPosition = this.overlay.position()
.connectedTo(
this.buttonRef,
{originX: 'start', originY: 'bottom'},
{overlayX: 'start', overlayY: 'top'}
)
return this.overlayPosition;
}
...
}
I made an example to show you how to use the CDK overlays services and classes.
Overlay demo
If you prefer the directive way Look at this medium article and check the examples applying the directive way:
Material CDK Overlay with RxJS

How add react-relay component to the storybook?

I am trying create a storybook for my react-realy app, but i don't know how to set mockup data for that component. For simple a component it is ok, because i can use dummy UI component vs Container approach, but i can't use this for nested relay components, for example there is a UserList component, which i want add to storybook, i can split relay fragment part to container and UI part to the component, but what if UserList children are too relay component? I can't split their when they are a part of the composition of UserList?
Is there some solution for add relay components to the storybook?
I created a NPM package called use-relay-mock-environment, which is based on relay-test-utils which allows you to make Storybook stories out of your Relay components.
It allows nesting of Relay components, so you can actually make stories for full pages made out of Relay components. Here's an example:
// MyComponent.stories.(js | jsx | ts | tsx)
import React from 'react';
import { RelayEnvironmentProvider } from 'react-relay';
import createRelayMockEnvironmentHook from 'use-relay-mock-environment';
import MyComponent from './MyComponentQuery';
const useRelayMockEnvironment = createRelayMockEnvironmentHook({
// ...Add global options here (optional)
});
export default {
title: 'MyComponent',
component: MyComponent,
};
export const Default = () => {
const environment = useRelayMockEnvironment({
// ...Add story specific options here (optional)
});
return (
<RelayEnvironmentProvider environment={environment}>
<MyComponent />
</RelayEnvironmentProvider>
);
};
export const Loading = () => {
const environment = useRelayMockEnvironment({
forceLoading: true
});
return (
<RelayEnvironmentProvider environment={environment}>
<MyComponent />
</RelayEnvironmentProvider>
);
};
You can also add <RelayEnvironmentProvider /> as a decorator, but I recommend not doing that if you want to create multiple stories for different states/mock data. In the above example I show 2 stories, the Default one, and a Loading one.
Not only that, it requires minimal coding, where you don't need to add the #relay-test-operation directive to your query, and the mocked data is automatically generated for you using faker.js, allowing you to focus on what matters, which is building great UI.
Feel free to review the source code here if you want to implement something similar: https://github.com/richardguerre/use-relay-mock-environment.
Note: it's still in its early days, so some things might change, but would love some feedback!
I also created relay-butler, which is a CLI that takes in GraphQL fragments and outputs Relay components, including a auto-generated query component that wraps the fragment component, and Storybook stories (the Default and Loading ones by default) that wrap that query component. And literally within minutes, I can create beautiful Relay components that are "documented" within Storybook.
Would also love some feedback for it!

Is it possible to add buttons and control its event to toolbar of inappbrowser using javascript...?

I am developing a Hybrid App for iOS and Android using PhoneGap.Is it possible to add buttons and control its event to toolbar of inappbrowser using javascript.I know how to add it through ios native side but i cant use that process.I need to control the button event through a javascript method.
You have two options to do that.
The first option is, obviously, to patch the native plugin code, and that's it. Here you can find an example made for iOS, you will have to do the same to your Android Java code and for every other platform you want to support.
Another option is to hide the native toolbar and inject HTML and CSS to create a new one when the page is loaded.
Something like this:
// starting inappbrowser...
inAppWindow = window.open(URL_TO_LOAD, '_blank', 'location=no');
// Listen to the events, we need to know when the page is completely loaded
inAppWindow.addEventListener('loadstop', function () {
code = CustomHeader.html();
// Inject your JS code!
inAppWindow.executeScript({
code: code
}, function () {
console.log("injected (callback).");
});
// Inject CSS!
inAppWindow.insertCSS({
code: CustomHeader.css
}, function () {
console.log("CSS inserted!");
});
And you will have obviously to define the CustomHeader object, something like this:
var CustomHeader = {
css: '#customheader { your css here }',
html: function() {
var code = 'var div = document.createElement("div");\
div.id = "customheader";\
// Insert it just after the body tag
if (document.body.firstChild){ document.body.insertBefore(div, document.body.firstChild); } \
else { document.body.appendChild(div); }';
return code;
}
};
I had experience with this problem.
For my case, the second option was enough, not a critical task. Sometimes it takes a lot for the loadstop event to fire, and so you don't see the injected bar for >= 5 seconds.
And you have to pay attention even on the CSS of the loaded page, because obviously you can affect the original CSS, or the original CSS can affect the style of your toolbar.

QNXStageWebView on top of all containers

I am developing an application for blackberry playbook using flex hero and html contents are displayed using QNXStageWebView.But the problem is when i add this webview to stage its coming on top of all other display objects.I am not able to control it.I need to solve it then only i can continue my work. can anyone help me to solve this issue???
import mx.core.FlexGlobals;
import mx.events.FlexEvent;
import qnx.media.QNXStageWebView;
public var webview:QNXStageWebView = new QNXStageWebView();
protected function view1_creationCompleteHandler(event:FlexEvent):void
{
var rect:Rectangle = new Rectangle(0,0,200,400);
webview.stage = stage;
webview.viewPort = rect;
webview.zOrder = 1;
}
]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<s:Button id="buttn" x="0" y="0"/>
when i run this code am not able to see that button. i need to display it on top of webview.
Have you tried removing the zOrder? It's like the z-order-property of html to arrange children in certain layers.
AFAIK, currently it just can't do that, all QNXStageWebView always placed on top of other object, we better wait for adobe / RIM to improve the implementation.

How to call a MXML class in ActionScript3.0 in Flex 3

I have a page made of custom components. In that page I have a button. If I click the button I have to call another page (page.mxml consisting of custom components). Then click event handler is written in Action-script, in a separate file.
How to make a object of an MXML class, in ActionScript? How to display the object (i.e. the page)?
My code:
page1.mxml
<comp:BackgroundButton x="947" y="12" width="61" height="22"
paddingLeft="2" paddingRight="2" label="logout" id="logout"
click="controllers.AdminSession.logout()"
/>
This page1.mxml has to call page2.mxml using ActionScript code in another class:
static public function logout():void {
var startPage:StartSplashPage = new StartSplashPage();
}
Your Actionscript class needs a reference to the display list in order to add your component to the stage. MXML is simply declarative actionscript, so there is no difference between creating your instance in Actionscript or using the MXML notation.
your function:
static public function logout():void {
var startPage:StartSplashPage = new StartSplashPage();
}
could be changed to:
static public function logout():StartSplashPage {
return new StartSplashPage();
}
or:
static public function logout():void {
var startPage:StartSplashPage = new StartSplashPage();
myReferenceToDisplayListObject.addChild( startPage );
}
If your actionscript does not have a reference to the display list, than you cannot add the custom component to the display list. Adding an MXML based custom component is no different than adding ANY other DisplayObject to the display list:
var mySprite:Sprite = new Sprite();
addChild(mySprite)
is the same as:
var startPage:StartSplashPage = new StartSplashPage();
myReferenceToDisplayListObject.addChild( startPage );
Both the Sprite and the StartSplashPage are extensions of DisplayObject at their core.
You reference MVC in the comments to another answer. Without knowing the specific framework you've implemented, or providing us with more code in terms of the context you are trying to perform this action in, it is difficult to give a more specific answer.
I assume that you are on a page with a set of components and want to replace this set of components on the page with a different set of components. My apologies in advance if this is not what you are trying to do.
You can do this using ViewStacks and switching the selected index on selection -- this can be done either by databinding or by firing an event in controllers.AdminSession.logout() and listening for that event in the Main Page and switching the selectedIndex of the view stack in the handler function.
MainPage.mxml
<mx:ViewStack>
<views:Page1...>
...
<comp:BackgroundButton x="947" y="12" width="61" height="22"
paddingLeft="2" paddingRight="2" label="logout" id="logout"
click="controllers.AdminSession.logout()"/>
</views:Page1...>
<views:Page2 ...>
...
<comp:Comp1 .../>
<comp:Comp2 .../>
</views:Page2>
I think you may use state to do you work.
You may take a look at http://blog.flexexamples.com/2007/10/05/creating-view-states-in-a-flex-application/#more-221
Edit:
I am not sure I fully understand your case.
As I know, you may make a new state in page1.mxml, and name it, eg. secondPageState, and then put the custom component page2.mxml in the secondPageState.
In the controller, you need an import statement to import the page1 component and make a public var for the page1 component, eg. firstPage.
Then, the code will similar to:
public function logout():voild
{
firstPage.currentState = "secondPageState";
}
Another solution:
If you don't like the change state solution, you may try to use the addchild, to add the custom component to your application.

Resources