release-debug configuration in React-Native - ios

Currently in React-Native, according to the documentation, to build your iOS app for production, you need to :
change your scheme to Release
change your AppDelegate.m to load the correct bundle
change your Info.pList for ATS
This is a strong violation of 12 factor config recommandation, and it leads to mistakes being made in a continuous integration process.
RN does not provide either out-of-the box strategies to know the configuration environment in the JS code, leading to the existence of the package react-native-config, which does a great job already, but is not perfect (Xcode is not fully supported).
Why is it so? Is it because there are actually so few RN app in production today that nobody cares about this? Can we do better than react-native-config so that steps listed above are not required? I would like a command line that archives my app in the same way that I can run cd android && ./gradlew assembleRelease, without changing anything to my config.
EDIT:
Fastlane makes deployment a lot easier through its gym command (thank you Daniel Basedow). Apparently, the philosophy of Xcode is to call environments "schemes", only you cannot store variables in them, or know which scheme you're running in your code... Anyway, David K. Hess found a great way to export your scheme name in your Info.plist, and then in your Objective C code, which means I'm now able to chose my bundle according to the current scheme, and not touch my code.
Here is my code:
NSString *schemeName = [[[NSBundle mainBundle] infoDictionary] valueForKey:#"SchemeName"];
if ([schemeName isEqualToString:#"scheme1"]) {
jsCodeLocation = [NSURL URLWithString:#"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];
} else if ([schemeName isEqualToString:#"scheme2"]) {
jsCodeLocation = [NSURL URLWithString:#"http://<my_local_ip_address>:8081/index.ios.bundle?platform=ios&dev=true"];
} else if ([schemeName isEqualToString:#"scheme3"]) {
jsCodeLocation = [[NSBundle mainBundle] URLForResource:#"main" withExtension:#"jsbundle"];
}
Now my problem is : I also want to know which scheme I'm running in my JS code. react-native-config's way is self-described as hacky, and overly complicated considering the fact the information is already in my Objective C code. Is there a way to bridge this information to my JS code?
Only knowing which scheme I'm running is not as good as being able to set environment variables, but at least I'll be able to switch between environments only by changing my scheme.
EDIT 2:
I managed to export my scheme to my JS code. I created a cocoa touch class with the following code:
// RNScheme.h
#import <Foundation/Foundation.h>
#import "RCTBridgeModule.h"
#interface RNScheme : NSObject <RCTBridgeModule>
#end
// RNScheme.m
#import "RNScheme.h"
#interface RNScheme()
#end
#implementation RNScheme
{
}
RCT_EXPORT_MODULE()
- (NSDictionary *)constantsToExport
{
NSString *schemeName = [[[NSBundle mainBundle] infoDictionary] valueForKey:#"SchemeName"];
NSLog(#"%#", schemeName);
return #{
#"scheme_name": schemeName,
};
}
#end
and then in my JS code:
import {NativeModules} from 'react-native'
let scheme = NativeModules.RNScheme.scheme_name
EDIT 3:
There is actually another way than using schemes. You can create new "configurations" ("Release" and "Debug" are called configurations) with the following steps (thanks CodePush):
Open up your Xcode project and select your project in the Project
navigator window
Ensure the project node is selected, as opposed to one of your
targets
Select the Info tab
Click the + button within the Configurations section and select
which configuration you want to duplicate
Then you can define keys with different values according to your configuration.
Select your app target
Chose Build Settings
Go to User-Defined section (at the bottom of the scroll area)
You can define constants with a different value according to your configuration (for instance API_ENDPOINT)
You can then reference this value in your Info.plist file :
Open your Info.plist file
Create a new value and give it a name (ApiEndpoint)
Give it the value $(API_ENDPOINT) or whatever name you gave to your constant
Now you can reference this value in your code using the code I gave you in my second edit to this question.
You can create one scheme per configuration to switch quickly from one to the other, or change the build configuration each time (option click on the run button).

In your AppDelegate you can use the correct bundle like this
#ifdef DEBUG
jsCodeLocation = [NSURL URLWithString:#"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];
#else
jsCodeLocation = [[NSBundle mainBundle] URLForResource:#"main" withExtension:#"jsbundle"];
#endif
When you do a release build, the DEBUG flag is not set. You can also use different files as your Info.plist depending on build type. There will probably be situations where you want an Xcode debug build with a production JS bundle or vice versa. In that case you need to touch code.
Building ios apps from command line can be a bit tricky. The problems you're describing are not specific to react-native but the Xcode build system. If you haven't already, check out fastlane especially the gym command. It is much simpler than using xcodebuild directly.
But you still have to define your schemes.

Related

Flavouring Objective-C iOS app with Storyboard

I have a single-activity iOS app in Objective-C with Storyboard. The app has two build schemes, say Scheme 1 and Scheme 2. The View has just a couple of buttons. I want to differentiate the color of these button depending on the build scheme.
I am new in the iOS world, but I am having a hard time parametrising the Storyboard (e.g. colors, strings, etc.) depending on the build scheme. I am aware of this post, but I'd like something more explicit.
Thank you for your help.
Conditional compilation with Schemes isn't supported by Xcode. You'd need to maintain two targets instead for that, and that gets messy quickly.
To do this with Schemes you need to maintain two asset catalogs with the correct named colors and copy the correct one in at build time. The source .xcasset catalogs would NOT be added to your target.
You need to add a Run Script early on in the Build Phases section of your target.
Luckily the Scheme name is expressed via the CONFIGURATION environment variable. You could do something like this, your paths might vary:
# Copy over the appropriate asset catalog for the scheme
target=${SRCROOT}/Resources/Colors.xcassets
if [ "${CONFIGURATION}" = "Scheme 1" ]; then
sourceassets=${PROJECT_DIR}/Scheme1.xcassets
else
sourceassets =${PROJECT_DIR}/Scheme2.xcassets
fi
if [ -e ${target} ]; then
echo "Assets: Purging ${target}"
rm -rf ${target}
fi
echo "Assets: Copying source=${sourceassets} to destination=${target}"
cp -r ${sourceassets} ${target}
Essentially you are replacing the compiled version of the asset catalog with one of your Scheme specific versions.
Strings would be another issue and you might be able to do this with the same technique for localised strings.
This all gets horrible very quickly and isn't recommended. Configuring the UI at runtime via code using the technique described in your referenced post would be a better bet.
You could construct a shim layer to protect your code from the Scheme changes.
e.g
#interface MyColors : NSObject
+ (UIColor *)buttonBackground;
#end
#implementation MyColors
+ (UIColor *)buttonBackground {
#if SCHEME1
return [UIColor colorNamed:#"scheme1ButtonBackground"];
#else
return [UIColor colorNamed:#"scheme2ButtonBackground"];
#endif
}
#end

How to disable font smoothing in Xcode 9?

I've got a great programming font Deccy that only looks good with font smoothing (anti aliasing) disabled in Xcode. With Xcode 8 the following would do the trick:
defaults write com.apple.dt.Xcode NSFontDefaultScreenFontSubstitutionEnabled -bool YES
defaults write com.apple.dt.Xcode AppleAntiAliasingThreshold 24
But this no longer works with Xcode 9.
Would it be possible to disable font smoothing in Xcode 9?
Mischief managed?
Here's a screenshot of my Xcode 9 with Deccy at 13pt:
I believe the above is what you want. Here's stock Xcode displaying the same file:
But how?
I probed deep for a noninvasive way to accomplish this, and failed. As far as I can tell, the text rendering path in Xcode 9 very deliberately turns on font smoothing.
Before going any further, please file a feature request with Apple. It only takes a few minutes, and it's your best hope for an answer that that can be discussed in front of those with sound security instincts and strained cardiovasculature:
https://bugreport.apple.com/
I wrote an Xcode plugin. You might have heard that Xcode 9 uses code signing restrictions to forbid the loading of plugins. This is true, but a few mavericks press on, and tonight we ride with them.
Step one
There is a tool, update_xcode_plugins. You can use it to strip the code signature from your copy of Xcode, and with it the bundle-loading restriction:
$ gem install update_xcode_plugins
$ update_xcode_plugins --unsign
If you change your mind, you can do this to revert to (a backup copy, I think?) of signed Xcode:
$ update_xcode_plugins --restore
Step two
There is another tool, Alcatraz. It's a plugin manager for Xcode. I chose to install it because it provides a plugin which provides a project template for plugins. I followed these instructions to install Alcatraz, which boil down to this:
$ git clone https://github.com/alcatraz/Alcatraz.git
$ cd Alcatraz
$ xcodebuild
I launched Xcode, clicked through the dialog warning me about the new plugin, and then used the newly-added Window > Package Manager to install the "Xcode Plugin" template.
Step three
I made a project with this template.
As I write this, the "Xcode Plugin" template hasn't been updated to support Xcode 9. No worries. I ran this command to grab Xcode 9's compatibility UUID:
$ defaults read /Applications/Xcode.app/Contents/Info DVTPlugInCompatibilityUUID
I visited my new project's Info.plist and added that UUID to the DVTPlugInCompatibilityUUIDs array.
Then, I linked SourceEditor.framework into my project. That was a two-step process:
Visit the target's Build Settings. Add this to Framework Search Paths:
/Applications/Xcode.app/Contents/SharedFrameworks/
Visit the target's Build Phases. Add a new "Link Binary With Libraries" phase. Hit the plus. Navigate to the directory above (you can just press the / key and then paste the path in) and choose SourceEditor.framework. It should appear in the list. The project should still build.
Then, I made my plugin's .mm file look like this (I deleted the .h file, it's unneeded for this PoC):
#import <AppKit/AppKit.h>
#include <dlfcn.h>
extern void CGContextSetAllowsFontAntialiasing(CGContextRef, BOOL);
static void hooked_sourceEditorSetFontSmoothingStyle(CGContextRef ctx) {
CGContextSetAllowsFontAntialiasing(ctx, NO);
}
#interface NoAA : NSObject
#end
#implementation NoAA
+ (void)pluginDidLoad:(NSBundle *)plugin
{
NSArray *allowedLoaders = [plugin objectForInfoDictionaryKey:#"me.delisa.XcodePluginBase.AllowedLoaders"];
if (![allowedLoaders containsObject:[[NSBundle mainBundle] bundleIdentifier]])
return;
Class cls = NSClassFromString(#"SourceEditorScrollView");
NSBundle* bundle = [NSBundle bundleForClass:cls];
void *handle = dlopen(bundle.executablePath.fileSystemRepresentation, RTLD_NOW);
if (!handle)
return;
uint8_t* set_font_smoothing_fn = dlsym(handle, "sourceEditorSetFontSmoothingStyle");
if (!set_font_smoothing_fn)
goto fin;
void* set_font_smoothing_fn_page = (void*)((long)set_font_smoothing_fn & -PAGE_SIZE);
if (mprotect(set_font_smoothing_fn_page, PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC))
goto fin;
set_font_smoothing_fn[0] = 0xe9; // jmp
uint32_t* jmp_arg = (uint32_t*)(set_font_smoothing_fn + 1);
*jmp_arg = (uint32_t)((long)hooked_sourceEditorSetFontSmoothingStyle - (long)(jmp_arg + 1));
mprotect(set_font_smoothing_fn_page, PAGE_SIZE, PROT_READ | PROT_EXEC);
fin:
dlclose(handle);
}
#end
…I think the gotos add character. Basically, it just defines a function that takes a CGContextRef and turns off text antialiasing for it. Then, it overwrites the beginning of a function inside the SourceEditor framework which ordinarily configures antialiasing settings — don't need that anymore — to jump to our function instead. It does all of this in an incredibly unsafe way, so if something goes wrong, Xcode may politely crash.
Build and run the project, which automatically installs the plugin. Accept the addition of your plugin, and that's that.
What now?
If anything here doesn't work because I messed up, let me know. I'm not planning to roll this into an Alcatraz plugin myself, but you or anyone else should free to do so with credit (after filing a feature request with Apple).
Happy hacking!
If you 'live' in XCode and need a crisp rendering of this TrueType font, then editing XCode application defaults with PrefEdit.app, or defaults write com.apple.dt.Xcode.* has no effect.
Thinking outside the box you might be interested in the following to achieve crispyness all-over your Mac.
Since the Deccy font is best viewed at 12pt, it makes sense to raise the AppleAntiAliasingThreshold in the global domain to 13, the default for this setting is 4.
You can also suggest no AppleFontSmoothing.
defaults write -g AppleFontSmoothing -int 0
defaults write -g AppleAntiAliasingThreshold -int 13
In addition to these tweaks a bit more can be achieved in the Accessibility Preference pane in System Preferences: The Display has 2 checkmarks that you should try: 'Differentiate without color', and 'Increase contrast'.
"Beauty is in the eye of the beholder", I hope this helps.
Here are alternative steps that might work for you.
Try to find the com.apple.dt.Xcode.plist file under Macintosh HD/Library/Preferences.
Copy the file to desktop
Open file and add NSFontDefaultScreenFontSubstitutionEnabled to (Boolean)YES
add AppleAntiAliasingThreshold to (Number)24
Replace this file with preference file
Restart the system and Xcode
Note: For safer side keep backup of the original file.

iOS XCode: How to load a value from a user-defined setting from build settings at run time

I've got the following constant in the code:
static NSString* const MyUrl = #"www.myurl.com";
Is it possible at all to create a user-defined setting and assign a value which can replace the value of MyUrl const at run time or during archive?
My situation is the following: I have a project with various targets. Each target points to a different URL in the code. It would be great if I could manage the URL through a user-defined setting rather than having to change the code every time I change the target.
Consider using info.plist for storing such values.
No you could not change the value of const variable.
You can change the value of URL string by simply NSString *myURL = #"www.url.com";
and use the global variable in AppDelegate and use that from any where in project.
You can use pre processor macros for this purpose. Goto to Xcode>Project>Target>Build Settings>Preprocessor Macros. Then for every build configuration (Debug, release, etc) add a target specific macro. In your source code, you can now identify your target, by just referring to the macro using #ifdef.
Example:
#ifdef TARGET1
static NSString* const MyUrl = #"www.myurl.com";
#endif
#ifdef TARGET2
static NSString* const MyUrl = #"www.myur2.com";
#endif
The following image depicts the declaration of TARGET macro in build settings-
I think you need to use a database or any other source out of your app. And fetch the URL string whenever it changes and you can use your NSString variable inside the code. You can use parse for your need (However, Parse will stop their service next year but there are lots of free online DBs you can use). With this method you don't need to change your code to update URL strings. I hope I could get what you need correctly.

Setup different target for using different constant in Xcode

I know maybe a question is duplicated, but where I can check information. How to setup different targets to build with different bundle name etc.
Right now I know of course how to create different targets in Xcode, it is very simple to copy it from example from first target that was created automatically when I created project.
But I also have Constant.h and Constant.m files. I want to handle constants depend on which target I build for.
Let's say when build for target A I need to setup NSString const *toEmail = #"a#test.com", but in case if I build for the target B then toEmail = #"b#test";
Do I need to create two difference Constant files say ConstantA.m and ConstantB.m or maybe there is another best practice here. I don't want to recreate a wheel )
I setup Preprocessor Macro in target build settings for just one target. (For Example: TARGET_B)
And in code i check using
// Check if it's target B:
#if TARGET_B
NSString const *toEmail = #"b#test.com"
#else
NSString const *toEmail = #"a#test.com"
#end

iOS: How to add conditionality based on Project Target

I am building a project that will eventually be compiled to make 4 slightly different applications. The main change I have to make are image changes, but I also want to change a few UILabels as well.
I know that I could create multiple XIB files and modify them to be attached to each target, but I would is there a way to use a macro #define if to detect the name of the target?
Example: Project: Project1
Targets: TargetA, TargetB
#define if Target = TargetA{
label.text = #"This is targetA";
}
Is this possible?
Thanks!
I found an easy way to do this... you can access the bundle identifier which is unique for each target:
NSLog(#"%#", [[NSBundle mainBundle] bundleIdentifier]);
C preprocess macro will accomplish this feature.
In Xcode:
TARGET->Build Settings->Preprocess Marcos
And your source code:
#if DEBUG
// debug build
#else
// release build
#endif
This is already defined macro. You can define your own macros in all project targets.

Resources