So in my previous article we bridged an Objective-C View component, I learned a lot, and also learned that I did a few unnecessary things. No worry though, we'll learn from that and make improvements in our Swift bridging.
First off want to give a thanks to Tony Xiao. He's been a great help in sharing code and giving explanations to a Swift and native development novice like myself. Also Asa Miller for writing some of the original code and always collaborating on whatever we're both working on.
We're going to bridge https://github.com/soffes/GradientView. Looks pretty nifty. Now we could also just use ReactART for this but we won't.
It looks more like JavaScript, it's nicer to read (aka less brackets), and seemingly easier to write? I don't know, they say it's the next big thing.
Well it can't be turned into a static library... wut? It seemingly can't be converted into one of those nice .xcodeproj
files with a .a
that we can easily link. However it can be converted into an .xcodeproj
with a .framework
we can link, not sure what the difference is but whatever. We'll still go about bridging. We could have also used Pods here, but once again the point of this post is about bridging and not Pods. I'll focus on those later.
I won't walk us through bundling up anything into a library, I'll just walk you through developing and integrating a Swift library.
So go ahead and fire off a react-native init GradientTest
to create an empty project.
Once that is done open up the project ios/GradientTest.xcodeproj
in XCode.
Now lets pull in the GradientView
library I had mentioned up above. I pulled it down and just put it in a GradientView
folder in the ios
directory.
Directory structure looks something like
We'll go through the same general process of linking the library as always.
Right click, select Add Files to GradientTest
and find the GradientView.xcodeproj
that we just pulled down.
When we go to link the library in Build Phases
like normal it will actually be GradientView.framework
that we are going to link. Should look something like so.
We'll still get some issues so lets go add $(SRCROOT)/GradientView
recursive to the Framework Search Paths
Should look something like
We're going to first start off by creating a new .swift
file. Simply do that by right clicking on our GradientTest
folder, select New file
and click on the swift selection and click create.
We'll call this file RNGradientViewManager
.
** CREATE THE BRIDGING HEADER**
This is super important. Our briding header file will be named GradientTest-Bridging-Header.h
. This allows us to bridge the React View Manager (Objective-C) code into the Swift world.
Or as you can read in the file
// // Use this file to import your target's public headers that you would like to expose to Swift. //
All we need to do is add our RCTViewManager.h
import. So our GradientTest-Bridging-Header.h
should just be this
// // Use this file to import your target's public headers that you would like to expose to Swift. // #import "RCTViewManager.h"
Okay now we need to add our Manager code, if you've read my previous tutorial before on bridging Objective-C, I've stated that the Manager is just a singleton View producer. Just because it is in swift is no different.
RNLinearGradientManager.swift
import Foundation @objc(RNLinearGradient) class RNGradientViewManager : RCTViewManager { override func view() -> UIView! { return UIView(); // We'll change this later } }
We create an override for the view
function, this is what React will call to produce the new view. Thanks to our bridging header we get access to RCTViewManager
and create a class called RNGradientViewManager
.
The important thing to call out here is the @objc(RNLinearGradientSwift)
line. This is an arbitrary name I made up, but what it says is to tell XCode and the compiler to expose the class RNGradientViewManager
to the Objective-C world and call it RNLinearGradientSwift
.
Well at some point we have to dive into a little Objective-C. We'll create a new class and call it RNLinearGradient
Then we'll subclass it off of RCTView
That will create 2 files.
RNLinearGradient.h
RNLinearGradient.m
This is where we will tell React Native about what we need and what to call our stuff in the JavaScript world.
RNLinearGradient.m
file.#import "RNLinearGradient.h" #import "RCTViewManager.h" @interface RCT_EXTERN_MODULE(RNLinearGradientSwift, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(locations, NSArray); RCT_EXPORT_VIEW_PROPERTY(colors, NSArray); @end
We import our RNLinearGradient.h
file we will get ot in a second, as well as our RCTViewManager.h
See there is our Manager we created and called RNLinearGradientSwift
.
We create a new interface and call RCT_EXTERN_MODULE
which will tell the JavaScript world about our module called RNLinearGradientSwift
.
We'll also export 2 view properties. These are specific to GradientView
. Both will be NSArray
and take an array of colors and locations to create the gradient.
RNLinearGradient.h
file.#import "RCTView.h" @interface RNLinearGradient : RCTView @property (nonatomic, assign) NSArray *locations; @property (nonatomic, assign) NSArray *colors; @end
We import RCTView
from React code, and then define our 2 properties.
Defining these properties here in this file tells React what properties to apply to the View that gets returned from our view
function call in our RNGradientViewManager
.
Alright now we are back to swift. We'll go ahead and create a new swift file that will be the view that gets returned from the view
function in our RNGradientViewManager
.
Create a swift file called RNGradientView
import Foundation import GradientView @objc(RNGradientView) class RNGradientView : GradientView { required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
We'll import our GradientView
library, and we'll create a class that inherits from it. Previously in my Objective-C tutorial we implemented the addSubviews
call however here we'll implement things a bit different and have the view that gets returned the actual GradientView
.
The whole required init?(coder aDecoder: NSCoder)
part was autogenerated by XCode, it's all magic to me.
Now lets add an init for the frame. The frame is the CGRect
that we get given. It tells us our origin x,y
and also the size width,height
of our view.
@objc(RNGradientView) class RNGradientView : GradientView { override init(frame: CGRect) { super.init(frame: frame); self.frame = frame; } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
We call super.init
with our frame, and then also assign self.frame = frame
. Remember because we inherited from GradientView
the self
basically refers to GradientView
so when we want to manipulate the GradientView
we simply manipulate our self
.
Lets add some setters now. We defined 2 different properties up above locations
and colors
. We now need to create our setters so we can receive the values and set them.
@objc(RNGradientView) class RNGradientView : GradientView { override init(frame: CGRect) { super.init(frame: frame); self.frame = frame; } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setLocations(locations: NSArray) { self.locations = locations.map({ return $0 as! CGFloat}); } func setColors(colors: NSArray) { self.colors = colors.map({return RCTConvert.UIColor($0)}) } }
Our locations
is an NSArray
of various values, they are sent over from the JavaScript world like so.
<LinearGradient locations={[0.2, 1]} />
, however they will be a mix of doubles
and integers
but we want everything as a CGFloat
which is what GradientView
is expecting.
From a JavaScript world you may be used to functional programming, we map
over the array which will return a new array. Then we cast whatever values double
or integer
, etc as CGFloat
and set that on self.locations
.
Then for colors
we'll do something similar. However in our JavaScript world we use the handy processColors
provided to use by React. Which will process arbitrary hex
, rgb
, color names, and others into a value that can easily be passed over the bridge.
The JavaScript code could look something like <LinearGradient colors={['red', 'rgb(255,100,50)']} />
.
We take advantage of the RCTConvert
from React which gives us a bunch of handy ways to convert arbitrary values into other values. In our case GradientView
expects UIColor
s in the shape of an array. So we can once again map
over our array of values and pass them into RCTConvert.UIColor
.
Remember we just left our Manager to return a boring ole UIView
, lets go fix it up to return our RNGradientView
.
import Foundation @objc(RNLinearGradientSwift) class RNGradientViewManager : RCTViewManager { override func view() -> UIView! { return RNGradientView(); } }
That sure was hard :)
We will need to write some JavaScript code but it should be pretty harmless.
import React, { requireNativeComponent, processColor } from "react-native"; let RNLinearGradient = requireNativeComponent( "RNLinearGradientSwift", LinearGradient ); class LinearGradient extends React.Component { render() { let { colors, ...otherProps } = this.props; return <RNLinearGradient {...otherProps} colors={processColor(colors)} />; } } LinearGradient.propTypes = { colors: React.PropTypes.array.isRequired, locations: React.PropTypes.array, }; export default LinearGradient;
We bring in React
, and our requireNativeComponent
as well as processColor
from React Native.
We create our RNLinearGradient
component that we render, and as you can see once again there is our RNLinearGradientSwift
that was externalized in our RNGradientViewManager
as RNLinearGradientSwift
.
We specify our propTypes
as arrays.
Then we can pull of colors
from this.props
, and we call processColor
with the array which will automatically map over and return a new array of all processed colors that will work nicely with the RCTConvert.UIColor
call.
<LinearGradient style={styles.gradient} locations={[0, 1.0]} colors={["#5ED2A0", "#339CB1"]} />
To use we simply define some values. locations
at [0,1]
will specify the first color at 0 position and the second color at the end which is 1.
This can take more than just 2 colors and locations, it will take any number of colors and locations.
<LinearGradient style={styles.gradient} locations={[0, 0.5, 1.0]} colors={["#5ED2A0", "red", "#339CB1"]} />
Our gradient style is just postioned absolutely so it is basically covering the background as a gradient.
gradient: { position: 'absolute', top: 0, left: 0, bottom: 0, right: 0, }
Awesome, we're done! You can check the full code up here https://github.com/asamiller/react-native-gradient. Yes it's not on my github repo, it's on a friends repo which we collaborated on together. Deal with it.