DEV Community

Sam
Sam

Posted on • Updated on

Shadows in NativeScript

Before we begin it's worth remembering, there's many ways to skin a cat.

😸 (🔪,✂️,🐶) 🙀

But for this post we're going to look at implementing a shadow in NativeScript using a single view; the label view shown below.

<!--
  main-page.xml

  This is the `xml` layout we'll be referring to throughout.
-->
<Page loaded="onLoaded" xmlns="http://schemas.nativescript.org/tns.xsd">
    <Label id="label" text="Shadow Games"/>
</Page>
Enter fullscreen mode Exit fullscreen mode

iOS Shadows

In the xml above you'll note that no backgroundColor or borderRadius have been set.

That's because on iOS the same view that's implementing a shadow can not also implement a backgroundColor or borderRadius (a.k.a. cornerRadius). Well it can, but they'll need to be set with the shadow, on the views underlying CALayer.

// main-page.js
function onLoaded(args) {
  const page = args.object;
  const label = page.getViewById("label");

  if(page.ios) {
    const layer = label.ios.layer;
    layer.backgroundColor = UIColor.whiteColor.CGColor;
    layer.shadowOffset = CGSizeMake(0, 1);
    layer.shadowOpacity = 1;
    layer.shadowRadius = 5;
    layer.cornerRadius = 20;
    /*
     You can also specify the shadow colour;
     (i.e. layer.shadowColor = UIColor.yellowColor.CGColor)

     But it will default to black if not set.
    */
  }
}
Enter fullscreen mode Exit fullscreen mode

And now we have a shadow in iOS

shadow-ios

Android Shadows

Since NativeScript 5.4 we have androidElevation. The feature is currently only available on Android, hence the name.

The androidElevation is an implementation of Material Designs elevation. To use it you just set the elevation and give your view a backgroundColor and a margin (and a borderRadius if you like that).

<Label 
  id="label" 
  text="Shadow Games"

  margin="10"
  borderRadius="20"
  androidElevation="12"
  backgroundColor="white"/>
Enter fullscreen mode Exit fullscreen mode
shadow-android shadow-ios--lost
Android shadow using elevation iOS shadow has been lost

Unfortunately, setting the backgroundColor and borderRadius will mean our iOS shadows will stop working. To fix that we can scope the offending properties to android.

<Label 
  id="label"
  text="Shadow Games"

  margin="10"
  android:borderRadius="20"
  androidElevation="12"
  android:backgroundColor="white"/>
Enter fullscreen mode Exit fullscreen mode

Voila, we have our cross platform shadows 🤘.

shadow-android shadow-ios
Android shadow iOS shadow

Where does it break down?

In a word, animation. On Android you won't have any problems animating the views backgroundColor, but on iOS you'll lose the shadow.

Thats because NativeScript animations animate the view properties, but we implemented the iOS backgroundColor (and borderRadius) on the views underlying CALayer. So in order to animate the backgroundColor we're going to have to animate it down there.

// ...
const layer = label.ios.layer;
const newColor = UIColor.yellowColor.CGColor;
const bgAnimation = CABasicAnimation.animationWithKeyPath("background");

bgAnimation.fromValue = layer.backgroundColor;
bgAnimation.toValue = newColor;
bgAnimation.duration = 2 // seconds

// triggers the animation to run.
layer.addAnimationForKey(bgAnimation, "bgAnimation");

// update layer prop to new value after animation, else it pops back to the initial value.
layer.backgroundColor = newColor;
Enter fullscreen mode Exit fullscreen mode

And if you made it this far, here's a Playground with the animations in full effect.

Top comments (2)

Collapse
 
cthulhu profile image
Catalin Ciubotaru

Thanks for the very useful post!
One small remark: the animationWithKeyPath should be:

CABasicAnimation.animationWithKeyPath('backgroundColor');

Collapse
 
mudlabs profile image
Sam

It seems to work with just “background”. Did you run into an issue using “background”, or is there some performance reason etc..?