Is there a way to compare bundle identifiers without using a hardcoded string? - swift

I'm working on a project where we have two different app versions that are being handled as different targets, both apps are very similar but they differ in some screens that have different elements. So we are retrieving the app bundle identifier and comparing it with a hardcoded string to decide the flows, I need help finding a way to avoid using a hardcoded value for the string that is used for comparison with the bundle identifier.
This is how we are retrieving the bundle identifier:
var appName: String { return stringValue(for: kCFBundleNameKey) }
This is how we are getting the target:
var currentTarget: Target {
return appName == "AppNumber1" ? .appnumber1 : .appnumber2
}
Target is an enum with 2 cases:
enum Target {
case appnumber1
case appnumber2
}
So what I would like to do is to avoid using the hardcoded string "AppNumber1" to compare with the bundle identifier. Is there any alternative?

If you use Xcode 10.3, you can define a unique Active Compilation Condition for each target (APPNUMBER1, APPNUMBER2) and then you can check in code what target is currently running. An example is shown below.
#if APPNUMBER1
//Code for "AppNumber1"
#elseif APPNUMBER2
//Code for "AppNumber2"
#endif

Related

What does the "\" notation do in the following Swift code snippet? [duplicate]

I'm new to backend Swift and thought I'd use Vapor to get up-and-running on a side project fast...
I ran vapor new WebServer --template=auth-template, and now I'm trying to figure out what something like return \.email means.
For more context, I'm looking in WebServer > Sources > App > Models > Users.swift:
import Authentication
import FluentSQLite
import Vapor
/// Allows users to be verified by basic / password auth middleware.
extension User: PasswordAuthenticatable {
/// See `PasswordAuthenticatable`.
static var usernameKey: WritableKeyPath<User, String> {
return \.email
}
// ...
}
And here is the definition of the User class:
/// A registered user, capable of owning todo items.
final class User: SQLiteModel {
// {omit extra code} ...
var email: String
// {omit extra code} ...
/// Creates a new `User`.
init(id: Int? = nil, name: String, email: String, passwordHash: String) {
// {omit extra code} ...
self.email = email
// {omit extra code} ...
}
}
What does this backslash-dot notation mean?
tl;dr: We take a look at the Swift language reference, and sure enough, the usage of this backslash-dot notation is called a key-path-expression.
(The question has been sufficiently answered, by this point.)
A more hands-on approach on how to get to that piece of buried documentation:
As you can see from the code you posted, the User class contains a property named email.
Notice that, assuming you're using Xcode, if you replace return \.email with return \, you get the compile-error "Expected expression path in Swift key path", so this is a hint that this backslash-dot notation might have to do with something called a key path.
From that documentation on key-path, we see that we could also have written \User.email (and you can try it out in Xcode with no compiler error).
Understanding the greater context of what's going on in that code:
So, semantically, to understand the meaning of the usernameKey declaration you're looking at, we might want to understand what a WritableKeyPath is. In simple, from the documentation, we see that a WritableKeyPath is: "A key path that supports reading from and writing to the resulting value."
So, we see that the usernameKey declaration takes in a WritableKeyPath object and returns a String that is User.email.
Furthermore, it's apparent that the User class needs this usernameKey property in order to conform to the PasswordAuthenticatable protocol, which was imported on the first line with import Authentication (if you care to explore there, take a look at Dependencies > Auth 2.0.0 > Authentication > Basic > BasicAuthenticatable.swift).

Flutter 1.22 Internationalization with variable as key

I implemented the new (official) localization for Flutter (https://pascalw.me/blog/2020/10/02/flutter-1.22-internationalization.html) and everything is working fine, except that I don't know how to get the translation for a variable key.
The translation is in the ARB file, but how can I access it?
Normally I access translations using Translations.of(context).formsBack, but now I would like to get the translation of value["labels"]["label"].
Something like Translations.of(context).(value["labels"]["label"]) does not work of course.
I don't think this is possible with gen_l10n. The code that is generated by gen_l10n looks like this (somewhat abbreviated):
/// The translations for English (`en`).
class TranslationsEn extends Translations {
TranslationsEn([String locale = 'en']) : super(locale);
#override
String get confirmDialogBtnOk => 'Yes';
#override
String get confirmDialogBtnCancel => 'No';
}
As you can see it doesn't generate any code to perform a dynamic lookup.
For most cases code generation like this is a nice advantage since you get auto completion and type safety, but it does mean it's more difficult to accommodate these kinds of dynamic use cases.
The only thing you can do is manually write a lookup table, or choose another i18n solution that does support dynamic lookups.
A lookup table could look something like this. Just make sure you always pass in the current build context, so the l10n code can lookup the current locale.
class DynamicTranslations {
String get(BuildContext context, String messageId) {
switch(messageId) {
case 'confirmDialogBtnOk':
return Translations.of(context).confirmDialogBtnOk;
case 'confirmDialogBtnCancel':
return Translations.of(context).confirmDialogBtnCancel;
default:
throw Exception('Unknown message: $messageId');
}
}
}
To provide an example for https://stackoverflow.com/users/5638943/kristi-jorgji 's answer (which works fine):
app_en.arb ->
{
"languages": "{\"en\": \"English\", \"ro\": \"Romanian\"}"
}
localization_controller.dart ->
String getLocaleName(BuildContext ctx, String languageCode) {
return jsonDecode(AppLocalizations.of(ctx)!.languages)[languageCode];
}
getLocaleName(context, 'ro') -> "Romanian"
You can store a key in translation as json string.
Then you read it, parse it to Map<string,string> and access dynamically what you need.
Been using this approach with great success

What is the point of GlobalMaterialLocalizations and flutter_localizations?

The Flutter article for internationalization states the following:
To add support for other languages, an application must specify additional MaterialApp properties, and include a separate package called flutter_localizations.
However, I do not understand what the point of flutter_localizations is and why I "must [...] include" it in my app if I want to add support for other languages.
If I take a look at GlobalMaterialLocalizations, which is what flutter_localizations apparently adds (along with the Widgets and Cupertino versions), I can only find a bunch of seemengly random strings I have never seen translated into a bunch of languages.
I have never seen these strings in my Flutter app.
Do I really need to include flutter_localizations if I want to internationalize my app, i.e. are these strings used anywhere by default?
Actually no.
It depends how you want to implement you internationalization.
You can get current local by:
/// Because of flutter issue
/// https://github.com/flutter/flutter/issues/38323
/// So return `locals[1]` instead of `window.locale`;
var locals = window.locales;
var local = (locals.length > 1) ? locals[1] : window.locale;
Benefit of current flutter_localizations
I think the benefit of this lib is that it also handle the text direction stuff in
The other way
If you only need to handle the Strings' translation, no need to handle text direction(ltr(left to right) or rtl(right to left)):
You handle your strings by the value returned above:
abstract class Strings {
String hello;
factory Strings() // => instance;
{
var locals = window.locales;
var local = (locals.length > 1) ? locals[1] : window.locale;
if (local != null) {
// log('Strings.load(${local.languageCode})');
if (local.languageCode == 'zh') {
return _StringsZH.instance;
}
}
return _StringsEN.instance;
}
}
class _StringsEN implements Strings {
String hello = 'Hello 2019';
_StringsEN._();
static _StringsEN instance = _StringsEN._();
}
class _StringsZH implements Strings {
String hello = '你好 2019';
_StringsZH._();
static _StringsZH instance = _StringsZH._();
}
And use it like
Strings().hello;

Can't Extend Swift Enum

I've the following base enum:
enum Voicity {}
On various different files, I extend the above enum to store various functions and information that is used by my app as a central station.
For instance, I use this enum to create my UI elements programmatically.
extension Voicity {
enum sectionObject {
case textField(ofType: Voicity.UIElement.textField)
case button(ofType: Voicity.UIElement.button)
case label(ofType: Voicity.UIElement.label)
case line()
case photoCircle
case stackView(_: UIStackView)
case view(_: UIView)
var item: UIView {
switch self {
case .textField(let type):
return createTextField(ofType: type)
case .label(let type):
return createLabel(ofType: type)
case .line():
return createLine()
case .photoCircle:
return createPhotoCircle()
case .stackView(let view):
return view
case .view(let view):
return view
case .button(let type):
return createButton(ofType: type)
}
}
}
}
For readability/maintainability, I separated the returned functions above into separate files as well by again extending the sectionObject enum.
extension Voicity.sectionObject {
func createLabel(ofType type: Voicity.UIElement.label) -> UILabel {
// implementation here
}
}
The above works fine but when I try to declare the createButton():
extension Voicity.sectionObject {
func createButton(ofType type: Voicity.UIElement.button) -> UIButton {
// implementation here
}
}
I get the following error:
sectionObject is not a member of Voicity.
Why can't I extend my enum on one file when I can do so in another?
Update
createButton() and createLabel() methods are both inside group/folder Model.
The enum declarations are inside group/folder Store.
So, they are separated. But it amazes me that createLabel() works when createButton() can't.
It all used to work(methods being in different files) when my enum cases were all in a single file again under group/folder Store and methods under group/folder Model. I needed to refactor my enum cases when they enlarged.
Moreover, I just now tried moving createButton() inside the extension declaring createLabel(). And it all works for some reason.
Previously, I deleted the file declaring the createButton() method and recreated it and everything is the same. Therefore it's not some weird Xcode parsing issue.
I found the solution here(nkukushkin's answer).
The Problem
I had the idea that it was a compilation order issue but thought that Xcode would be automatically handling it by itself. Therefore I didn't even bother to go to my project settings. Turns out, it might not always do so.
How to Solve
Select your project,
Select your target,
Go to your target's Build Phases,
Extend Compile Sources.
Here, you're seeing a list of the files being compiled in your project.
In my case, the file I initially defined my enum sectionObject was listed under the files which I extended that enum. Therefore I had to move the definition file to the top so that the extensions can see it.
Unfortunately, Xcode(8.2.1 (8C1002)) does not allow drag-n-drop. I had to remove the file and re-add it by the plus/minus signs beneath the Compile Sources list. Once you re-add it, it gets listed on the top.

Cannot convert value of type 'X' to expected argument type 'X'

Xcode 8 and Swift 3 made me really sad today :(
Please have a look and tell me if you ever had something like this and if it's possible to fix it. I've been trying different solutions, among them:
Cmd + Shift + K
Cmd + Shift + Option + K
Delete Derived Data
Change used struct (it's a nested struct in my code), flattened it, change to really basic one
Update 1:
Here's the code (although I think it's not necessarily a problem related to my implementation), it's in my tests target:
let viewModelStub: Quiz.NewRoundDetails.ViewModel = Quiz.NewRoundDetails.ViewModel(roundNumber: "", score: "", proposedAnswerParts: [
Quiz.NewRoundDetails.ViewModel.AnswersForComponent(answers: []),
Quiz.NewRoundDetails.ViewModel.AnswersForComponent(answers: []),
Quiz.NewRoundDetails.ViewModel.AnswersForComponent(answers: [])])
_ = viewController.view
viewController.display(roundModel: viewModelStub)
Here are structs:
struct Quiz {
struct NewRoundDetails {
struct Response {
let roundNumber: Int
let score: Int
}
struct ViewModel {
let roundNumber: String
let score: String
let proposedAnswerParts: [Quiz.NewRoundDetails.ViewModel.AnswersForComponent]
struct AnswersForComponent {
let answers: [String]
}
}
}
}
And in viewController things look like this:
func display(roundModel: Quiz.NewRoundDetails.ViewModel) {
...
}
Nothing so unusual I think. I have just discovered one more thing - the code works fine on the app target side, it doesn't work on tests target.
I don't have more ideas atm... Can you help me? I have created radar as well
Check this answer: Stack Overflow
It's probably an issue with targeting. If you're using the #testable key word on the top of your test file and your main project files also target your tests then the tests are referring to two different files. So your files should only target either the tests (if they're a test file) or the main project. If they have both checked uncheck one and use the #testable import [your project's name] at the top of your test file.
Ok, I got it...
I've imported my app's module with #testable and also I've had added my .swift file with model classes to the test target. Probably <MyTestModule>. Quiz.NewRoundDetails.ViewModel have been created instead of <MyAppModule>. Quiz.NewRoundDetails.ViewModel