Different .listStyle() on iOS and WatchOS in a reusable component? - swiftui

I'd love to reuse a list layout between iOS and WatchOS, but InsetGroupedListStyle() isn't available on WatchOS.
What would be a good way to create a helper that would conditionally return InsetGroupedListStyle() on iOS and eg. PlainListStyle() on WatchOS?
I tried this, but get an error that I cannot return ListStyle (which is probably caused by SwiftUI needing to know a specific type in compile time).
View.swift
List {
// ...
}
.listStyle(MyInsetGroupedListStyle())
Helpers.swift
public func MyInsetGroupedListStyle() -> ListStyle {
#if os(watchOS)
return PlainListStyle()
#else
return InsetGroupedListStyle()
#endif
}
Alternate way could be specifying the listStyle inline, but swift doesn't support conditional compilation in expressions:
View.swift
List {
// ...
}
.listStyle(#if os(watchOS) PlainListStyle() #else InsetGroupedListStyle() #endif)

You can achieve what you want using an extension on View. This allows you to add the listStyle modifier with the parameter that you want for the OS that you want.
extension View {
public func customListStyle() -> some View {
#if os(watchOS)
return self.listStyle(PlainListStyle())
#else
return self.listStyle(InsetGroupedListStyle())
#endif
}
}
You would then use it like this:
List {
// items in list go here
}
.customListStyle()

Use
an opaque type
public var myInsetGroupedListStyle: some ListStyle {
#if os(watchOS)
PlainListStyle()
#else
InsetGroupedListStyle()
#endif
}
a closure
listStyle( {
#if os(watchOS)
PlainListStyle()
#else
InsetGroupedListStyle()
#endif
} () )

Related

C++ constexpr boolean vs macro

I have been reading about using constexpr instead of using macros for better safety, as macros can lead to undefined behavior. However, if I have the code in the following example, is it okay to do this, and just use preprocessor if statements (#if), or should I change the macros to constexpr bool.
Here is what I wanted to do:
#if NDEBUG
#define ENABLE_VALIDATION_LAYERS 0
#else
#define ENABLE_VALIDATION_LAYERS 1
#endif
vs
#if NDEBUG
constexpr bool enable_validation_layers = false;
#else
constexpr bool enable_validation_layers = true;
#endif
Code usage right now looks like this:
//if validation layers are not enabled, the validation support does not need to be checked
#if ENABLE_VALIDATION_LAYERS
if (!CheckValidationLayerSupport()) //function that returns a bool to check if validation layers are supported
{
throw std::runtime_error("validation layers requested, but not available!");
}
#endif

How can I macro #define a static method call in C++?

I am trying to write software that can behave differently based on whether or not a certain component should be simulated in software or execute on real hardware. However, GCC complains that the Scope resolution operator (::) cannot be used in a macro, so my question is: is it possible to define a macro to a static method call?
My goal is to be able to, using another preprocessor define, choose between using all real components (0), using all simulated components (1), or using a mix of real and simulated components (2). This last case is where I am running into this issue. Under this condition, I want to call a function which I am "protecting" by implementing it as a static method. Here is my approach:
#define SIM_CONF 2
#if SIM_CONF == 0
#define IS_HW_SIMULATED(name) false
#define IS_HW_REAL(name) true
#endif
#if SIM_CONF == 1
#define IS_HW_SIMULATED(name) true
#define IS_HW_REAL(name) false
#endif
#if SIM_CONF == 2
#define IS_HW_SIMULATED(name) SimConfig::isSimulated(name)
#define IS_HW_REAL(name) SimConfig::isReal(name)
#endif
class SimConfig
{
public:
static bool isSimulated(const char* szName);
static bool isReal(const char* szName);
};
EDIT: Here's an example of how I use it elsewhere:
void PumpComponent::commandRevs(float revs)
{
#if IS_HW_SIMULATED("PumpComponent")
// do simulation procedure
#else
// do real hardware procedure
#endif
}
When I compile, GNU Make complains:
error: token "::" is not valid in preprocessor expressions
#define IS_HW_SIMULATED(name) SimConfig::isSimulated(name)
Is there some approach where I can protect/encapsulate the isSimulated() and isReal() functions, and still be able to refer to them in preprocessor directives?
Problem is how you use this macro. You have placed it as preprocessor #if argument.
Processor do not understand code and argument of #if must be something what processor can handle, so macros and literals.
SimConfig::isSimulated is a code which in not defined yet. It will be know during compilation process, so after preprocessing is completed.
One way to fix it is simply use if else
void PumpComponent::commandRevs(float revs)
{
if IS_HW_SIMULATED("PumpComponent") {
// do simulation procedure
} else {
// do real hardware procedure
}
}
It is not problem for compiler. it will noticed that this is constant and should remove obsolete code.
Other way to fix it is to abandon macros. You can use templates.
Or enclose macro depended stuff in some class and use macros to alter that class functionality (this way this macros will not spread all over your code).
Don't use #if for this. Just write normal code:
void PumpComponent::commandRevs(float revs)
{
if (IS_HW_SIMULATED("PumpComponent")) {
// do simulation procedure
} else {
// do real hardware procedure
}
}
The compiler will delete one of the branches when SIM_CONF is 0 or 1, since the branch condition is a compile-time constant. It will keep the branches when it's 2.
However, I don't see a reason to have the IS_HW_SIMULATED and IS_HW_REAL macros at all. Looking at the code you posted, it seems you only need one function: SimConfig::isSimulated():
bool SimConfig::isSimulated(const char* szName)
{
#if SIM_CONF == 1
(void)szName; // supress "unused parameter" warning
return true;
#else
// Your normal implementation.
#endif
}
The rest of your code doesn't need to use any macros then:
void PumpComponent::commandRevs(float revs)
{
if (SimConfig::isSimulated("PumpComponent")) {
// do simulation procedure
} else {
// do real hardware procedure
}
}
SimConfig::isReal() doesn't seem to serve any purpose.

How to make a callback function pointer from C++ to Swift?

There are many tutorials to make this on web but none are clear and objective, because thats I'm prefer show my case.
I'm developing a iOS app that use a C API to connect to a service web and it have callback functions, my task is simple, I must get events generateds in C code on Swift code. To this I tried some ways, the current way that I'm trying is the follow.
Scenario
I've three files: wrapper.cpp, Bridging-Header.h and ViewController.swift
On Briding-Header.h I declared a function's pointer.
Bridging-Header.h
void callback_t(void(*f)(unsigned char*));
On wrapper.cpp I wrote my code to connect to web and use callback functions. So this is the callback method to get state connection when is disconnected.
wrapper.cpp
void callback_web_disconnected(unsigned char *c) {
printf("Disconnected %s",c);
// Here I want invoke a method to Swift code
callback_t(r); // But this not works
}
It's not compiles, error message: Use of undeclared identifier 'callback_t'.
If I try write: void callback_frame(unsigned char*);, linker command failed with exit code 1 error occurs.
ViewController.swift
func callback_t(_ f: ((UnsafeMutablePointer<UInt8>?) -> Void)!) {
print("ARRIVED ON VIEWCONTROLLER!")
// Here I want get the error code (unsigned char*) passed as parameter
}
I can't import Bridging-Header.h file on wrapper.cpp because cause many conflicts.
First, callback_t is not an identifier. I don't see it typedef anywhere..
Second, you need some way of telling C++ that the callback is your swift function. To do that, I pass it as a parameter to the C++ function similar to how we do it in Objective-C and Swift.. Otherwise you need to store the callback in a global variable somewhere and have C++ access it.
Using the first method of passing the callback as a parameter:
First in the C++ header (Foo.h) I did (Do NOT remove the ifdef stuff.. the compiler uses C linkage when importing to Swift but when compiling the C++ side, it'll be mangled so to make it use C linkage, we extern "C" the code):
#ifndef Foo_hpp
#define Foo_hpp
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef void(*callback_t)(const char *);
void callback_web_disconnected(callback_t);
#ifdef __cplusplus
} // extern "C"
#endif
#endif /* Foo_hpp */
Then in the implementation file (Foo.cpp) I did:
#include "Foo.h"
#include <thread>
#include <iostream>
#ifdef __cplusplus
extern "C" {
#endif
void callback_web_disconnected(callback_t callback)
{
std::thread t = std::thread([callback] {
std::this_thread::sleep_for(std::chrono::seconds(2));
if (callback)
{
callback("Hello World");
}
});
t.detach();
}
#ifdef __cplusplus
} // extern "C"
#endif
Then in ViewController.swift I did:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
callback_web_disconnected({
if let ptr = $0 {
let str = String(cString: ptr)
print(str)
}
})
}
}
It works fine. The Swift code gets called from C++ after 2 seconds has passed.
Using the second method of storing the callback in a global variable (which I despise but let's not get into that)..
In Foo.h I did:
#ifndef Foo_hpp
#define Foo_hpp
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef void(*callback_t)(const char *);
callback_t globalCallback; //Declare a global variable..
void callback_web_disconnected();
#ifdef __cplusplus
} // extern "C"
#endif
#endif /* Foo_hpp */
In Foo.cpp I did:
#include "Foo.h"
#include <thread>
#include <iostream>
#ifdef __cplusplus
extern "C" {
#endif
void callback_web_disconnected()
{
std::thread t = std::thread([] {
std::this_thread::sleep_for(std::chrono::seconds(2));
if (globalCallback)
{
globalCallback("Hello World");
}
});
t.detach();
}
#ifdef __cplusplus
} // extern "C"
#endif
In ViewController.swift, I did:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//Set the globalCallback variable to some block or function we want to be called..
globalCallback = {
if let ptr = $0 {
let str = String(cString: ptr)
print(str)
}
}
//Trigger our test.. I guess your C++ code will be doing this anyway and it'll call the globalCallback.. but for the sake of this example, I've done it here..
callback_web_disconnected()
}
}

Preprocessor macros for different function names

I want to write some code to be compatible with different boost versions, and want to use appropriate functions for the given boost versions. Right now I am trying,
#if BOOST_VERSION>105000
#define boost_sleep boost::this_thread::sleep_for
#define millisectime boost::chrono::milliseconds
#define timed_join try_join_for
#else
#define boost_sleep boost::this_thread::sleep
#define millisectime boost::posix_time::milliseconds
#endif
Which seem to compile fine. I am using it in the code with something like,
// Wait for no reason,
boost_sleep(millisectime(1000));
if( !(workerThread->timed_join(millisectime(1000)) )){
cout << "Not joined on time" << endl;
workerThread->detach();
}
Is there a better/standard way to do this? any suggestions to improve this?
The macro works, but has the problem that you might accidentally replace something other than the function of boost. Maybe one of the third party headers that you include happen to define a variable, or a function or anything with identifier timed_join or millisectime. Perhaps that definition is in an undocumented implementation detail namespace.
Macro replacement for types: A type alias.
typedef boost::
#if BOOST_VERSION>105000
chrono
#else
posix_time
#endif
::milliseconds millisectime;
Macro replacement for functions: A wrapper function.
void boost_sleep(millisectime m) {
return boost::this_thread::sleep
#if BOOST_VERSION>105000
_for
#endif
(m);
}
Wrapping the member function will change the usage slightly
void timed_join(boost_thread_type& t, millisectime m) {
t->
#if BOOST_VERSION>105000
try_join_for
#else
timed_join
#endif
(m);
}
Usage:
timed_join(workerThread, millisectime(1000));
Your definitions are aliases; C++ doesn't need a preprocessor for that.
I.e.
#if BOOST_VERSION>105000
using millisectime = boost::chrono::milliseconds;
void boost_sleep(millisectime t) { boost::this_thread::sleep_for(t); }
#else
...

using #if to use old / new method code

I have question is possible use #if to use old / new method.
Example:
#if (Old)
public void method1()
{
//code
}
#else
public void method1()
{
//code
}
#endif
If you're referring to C#:
You can do this, see here for an example:
http://msdn.microsoft.com/en-us/library/aa691099(v=vs.71).aspx
Remove the brackets around Old in your example, and #define Old or don't.
Typically you would define the directives in the project properties under a build configuration.