diff --git a/.gitignore b/.gitignore
index d3e704a..90e098a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,3 +27,6 @@ node_modules/
venv/
*.pyc
.mypy_cache/
+
+## Swift
+xcuserdata/
\ No newline at end of file
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index 4623f48..e6972bb 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -60,6 +60,7 @@ Here are the checklists for the various languages:
- [C++](cpp.md)
- [TypeScript](typescript.md)
- [Java](java.md)
+- [Swift](swift.md)
By contributing to this repository you agree that all of your work will be governed by the terms of the [LICENSE](../LICENSE) file.
diff --git a/docs/directory-structure.md b/docs/directory-structure.md
index b375c08..d73467c 100644
--- a/docs/directory-structure.md
+++ b/docs/directory-structure.md
@@ -183,6 +183,7 @@ If you want to contribute to a language not listed here please add it to the lis
- `typescript/`
- `java/`
- `octave/`
+- `swift/`
## [Directory `tools/`](../tools/)
diff --git a/docs/swift.md b/docs/swift.md
new file mode 100644
index 0000000..57fc984
--- /dev/null
+++ b/docs/swift.md
@@ -0,0 +1,7 @@
+# Swift Checklist
+
+This is a checklist for contributing Swift example code to this repository.
+
+## Tool & Runtime Versions
+
+We recommend using the latest version of XCode or Swift Package Manager.
diff --git a/examples/gui_swift_ui/README.md b/examples/gui_swift_ui/README.md
new file mode 100644
index 0000000..9387c83
--- /dev/null
+++ b/examples/gui_swift_ui/README.md
@@ -0,0 +1,28 @@
+# Simple GUI using SwiftUI
+
+This example demonstrates using the [SwiftUI Framework](https://developer.apple.com/xcode/swiftui/)
+to create a simple Interface to control a Zaber device.
+
+
+
+## Hardware Requirements
+
+Any Zaber linear motion device connected to a serial port.
+
+## Dependencies / Software Requirements / Prerequisites
+
+- MacOS 14.5+
+- [XCode 16+](https://developer.apple.com/xcode/)
+
+## Configuration / Parameters
+
+The serial port to connect to can be entered into the input box after script startup.
+
+Optionally, you can edit the following constants in the script before running the script:
+
+- `DeviceConstants.deviceAddress`: The device address of the device you'd like to connect to
+- `DeviceConstants.axisNumber`: The axis number of the axis you'd like to control on the device (`1` for most integrated devices)
+
+## Running the Script
+
+To run the script, open the project in XCode and press the `Start` button.
diff --git a/examples/gui_swift_ui/SwiftUiExample.xcodeproj/project.pbxproj b/examples/gui_swift_ui/SwiftUiExample.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..f37ca50
--- /dev/null
+++ b/examples/gui_swift_ui/SwiftUiExample.xcodeproj/project.pbxproj
@@ -0,0 +1,358 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 77;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 1DE422902DB0706000F6A81D /* ZaberMotionLib in Frameworks */ = {isa = PBXBuildFile; productRef = 1DE4228F2DB0706000F6A81D /* ZaberMotionLib */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 1D6BB6592DA86B610086489A /* SwiftUiExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftUiExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFileSystemSynchronizedRootGroup section */
+ 1D6BB65B2DA86B610086489A /* SwiftUiExample */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ path = SwiftUiExample;
+ sourceTree = "";
+ };
+/* End PBXFileSystemSynchronizedRootGroup section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 1D6BB6562DA86B610086489A /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 1DE422902DB0706000F6A81D /* ZaberMotionLib in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 1D6BB6502DA86B610086489A = {
+ isa = PBXGroup;
+ children = (
+ 1D6BB65B2DA86B610086489A /* SwiftUiExample */,
+ 1D6BB65A2DA86B610086489A /* Products */,
+ );
+ sourceTree = "";
+ };
+ 1D6BB65A2DA86B610086489A /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 1D6BB6592DA86B610086489A /* SwiftUiExample.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 1D6BB6582DA86B610086489A /* SwiftUiExample */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 1D6BB6682DA86B620086489A /* Build configuration list for PBXNativeTarget "SwiftUiExample" */;
+ buildPhases = (
+ 1D6BB6552DA86B610086489A /* Sources */,
+ 1D6BB6562DA86B610086489A /* Frameworks */,
+ 1D6BB6572DA86B610086489A /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ fileSystemSynchronizedGroups = (
+ 1D6BB65B2DA86B610086489A /* SwiftUiExample */,
+ );
+ name = SwiftUiExample;
+ packageProductDependencies = (
+ 1DE4228F2DB0706000F6A81D /* ZaberMotionLib */,
+ );
+ productName = SwiftUiExample;
+ productReference = 1D6BB6592DA86B610086489A /* SwiftUiExample.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 1D6BB6512DA86B610086489A /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = 1;
+ LastSwiftUpdateCheck = 1620;
+ LastUpgradeCheck = 1620;
+ TargetAttributes = {
+ 1D6BB6582DA86B610086489A = {
+ CreatedOnToolsVersion = 16.2;
+ };
+ };
+ };
+ buildConfigurationList = 1D6BB6542DA86B610086489A /* Build configuration list for PBXProject "SwiftUiExample" */;
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 1D6BB6502DA86B610086489A;
+ minimizedProjectReferenceProxies = 1;
+ packageReferences = (
+ 1DE4228E2DB0706000F6A81D /* XCRemoteSwiftPackageReference "zaber-motion-lib-swift" */,
+ );
+ preferredProjectObjectVersion = 77;
+ productRefGroup = 1D6BB65A2DA86B610086489A /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 1D6BB6582DA86B610086489A /* SwiftUiExample */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 1D6BB6572DA86B610086489A /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 1D6BB6552DA86B610086489A /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ 1D6BB6662DA86B620086489A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MACOSX_DEPLOYMENT_TARGET = 14.7;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = macosx;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ 1D6BB6672DA86B620086489A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MACOSX_DEPLOYMENT_TARGET = 14.7;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = macosx;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ };
+ name = Release;
+ };
+ 1D6BB6692DA86B620086489A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = SwiftUiExample/SwiftUiExample.entitlements;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_ASSET_PATHS = "\"SwiftUiExample/Preview Content\"";
+ DEVELOPMENT_TEAM = 5J57F75W2Y;
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 14.6;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = zaber.SwiftUiExample;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 6.0;
+ };
+ name = Debug;
+ };
+ 1D6BB66A2DA86B620086489A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = SwiftUiExample/SwiftUiExample.entitlements;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_ASSET_PATHS = "\"SwiftUiExample/Preview Content\"";
+ DEVELOPMENT_TEAM = 5J57F75W2Y;
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 14.6;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = zaber.SwiftUiExample;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 6.0;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 1D6BB6542DA86B610086489A /* Build configuration list for PBXProject "SwiftUiExample" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 1D6BB6662DA86B620086489A /* Debug */,
+ 1D6BB6672DA86B620086489A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 1D6BB6682DA86B620086489A /* Build configuration list for PBXNativeTarget "SwiftUiExample" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 1D6BB6692DA86B620086489A /* Debug */,
+ 1D6BB66A2DA86B620086489A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+
+/* Begin XCRemoteSwiftPackageReference section */
+ 1DE4228E2DB0706000F6A81D /* XCRemoteSwiftPackageReference "zaber-motion-lib-swift" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/zabertech/zaber-motion-lib-swift";
+ requirement = {
+ kind = upToNextMajorVersion;
+ minimumVersion = 7.6.1;
+ };
+ };
+/* End XCRemoteSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ 1DE4228F2DB0706000F6A81D /* ZaberMotionLib */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 1DE4228E2DB0706000F6A81D /* XCRemoteSwiftPackageReference "zaber-motion-lib-swift" */;
+ productName = ZaberMotionLib;
+ };
+/* End XCSwiftPackageProductDependency section */
+ };
+ rootObject = 1D6BB6512DA86B610086489A /* Project object */;
+}
diff --git a/examples/gui_swift_ui/SwiftUiExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/gui_swift_ui/SwiftUiExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/examples/gui_swift_ui/SwiftUiExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/examples/gui_swift_ui/SwiftUiExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/examples/gui_swift_ui/SwiftUiExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
new file mode 100644
index 0000000..bc042fd
--- /dev/null
+++ b/examples/gui_swift_ui/SwiftUiExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -0,0 +1,87 @@
+{
+ "originHash" : "457e295fa789fb85f2c78d748c50748b560c77e1f03a008eb8c2944ad6e89c3b",
+ "pins" : [
+ {
+ "identity" : "nimble",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/Quick/Nimble.git",
+ "state" : {
+ "revision" : "7a46a5fc86cb917f69e3daf79fcb045283d8f008",
+ "version" : "8.1.2"
+ }
+ },
+ {
+ "identity" : "swift-atomics",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-atomics.git",
+ "state" : {
+ "revision" : "cd142fd2f64be2100422d658e7411e39489da985",
+ "version" : "1.2.0"
+ }
+ },
+ {
+ "identity" : "swift-bson",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/mongodb/swift-bson",
+ "state" : {
+ "revision" : "d18d9798987b737a7162ce6678da18561b5c629a",
+ "version" : "3.1.0"
+ }
+ },
+ {
+ "identity" : "swift-collections",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-collections.git",
+ "state" : {
+ "revision" : "671108c96644956dddcd89dd59c203dcdb36cec7",
+ "version" : "1.1.4"
+ }
+ },
+ {
+ "identity" : "swift-extras-base64",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/swift-extras/swift-extras-base64",
+ "state" : {
+ "revision" : "778e00dd7cc2b7970742f061cffc87dd570e6bfa",
+ "version" : "0.5.0"
+ }
+ },
+ {
+ "identity" : "swift-extras-json",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/swift-extras/swift-extras-json",
+ "state" : {
+ "revision" : "122b9454ef01bf89a4c190b8fd3717ddd0a2fbd0",
+ "version" : "0.6.0"
+ }
+ },
+ {
+ "identity" : "swift-nio",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-nio",
+ "state" : {
+ "revision" : "c51907a839e63ebf0ba2076bba73dd96436bd1b9",
+ "version" : "2.81.0"
+ }
+ },
+ {
+ "identity" : "swift-system",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-system.git",
+ "state" : {
+ "revision" : "a34201439c74b53f0fd71ef11741af7e7caf01e1",
+ "version" : "1.4.2"
+ }
+ },
+ {
+ "identity" : "zaber-motion-lib-swift",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/zabertech/zaber-motion-lib-swift",
+ "state" : {
+ "revision" : "1b0b07993dd5b80f0e213b8ed853253427ed4fc6",
+ "version" : "7.6.1"
+ }
+ }
+ ],
+ "version" : 3
+}
diff --git a/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AccentColor.colorset/Contents.json b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..eb87897
--- /dev/null
+++ b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..f1064ea
--- /dev/null
+++ b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,68 @@
+{
+ "images" : [
+ {
+ "filename" : "icon 9.png",
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "16x16"
+ },
+ {
+ "filename" : "icon 8.png",
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "16x16"
+ },
+ {
+ "filename" : "icon 7.png",
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "32x32"
+ },
+ {
+ "filename" : "icon 6.png",
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "32x32"
+ },
+ {
+ "filename" : "icon 5.png",
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "128x128"
+ },
+ {
+ "filename" : "icon 4.png",
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "128x128"
+ },
+ {
+ "filename" : "icon 3.png",
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "256x256"
+ },
+ {
+ "filename" : "icon 2.png",
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "256x256"
+ },
+ {
+ "filename" : "icon 1.png",
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "512x512"
+ },
+ {
+ "filename" : "icon.png",
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "512x512"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 1.png b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 1.png
new file mode 100644
index 0000000..70e0342
Binary files /dev/null and b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 1.png differ
diff --git a/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 2.png b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 2.png
new file mode 100644
index 0000000..70e0342
Binary files /dev/null and b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 2.png differ
diff --git a/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 3.png b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 3.png
new file mode 100644
index 0000000..6aa4d80
Binary files /dev/null and b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 3.png differ
diff --git a/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 4.png b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 4.png
new file mode 100644
index 0000000..6aa4d80
Binary files /dev/null and b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 4.png differ
diff --git a/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 5.png b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 5.png
new file mode 100644
index 0000000..e2c39ab
Binary files /dev/null and b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 5.png differ
diff --git a/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 6.png b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 6.png
new file mode 100644
index 0000000..45cb862
Binary files /dev/null and b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 6.png differ
diff --git a/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 7.png b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 7.png
new file mode 100644
index 0000000..9a16827
Binary files /dev/null and b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 7.png differ
diff --git a/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 8.png b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 8.png
new file mode 100644
index 0000000..9a16827
Binary files /dev/null and b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 8.png differ
diff --git a/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 9.png b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 9.png
new file mode 100644
index 0000000..2ff720d
Binary files /dev/null and b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon 9.png differ
diff --git a/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon.png b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon.png
new file mode 100644
index 0000000..843f8d0
Binary files /dev/null and b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/AppIcon.appiconset/icon.png differ
diff --git a/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/Contents.json b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/logo.imageset/Contents.json b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/logo.imageset/Contents.json
new file mode 100644
index 0000000..a8805ca
--- /dev/null
+++ b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/logo.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "logo.svg",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "logo 1.svg",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "logo 2.svg",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/logo.imageset/logo 1.svg b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/logo.imageset/logo 1.svg
new file mode 100644
index 0000000..2327567
--- /dev/null
+++ b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/logo.imageset/logo 1.svg
@@ -0,0 +1,36 @@
+
+
+
diff --git a/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/logo.imageset/logo 2.svg b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/logo.imageset/logo 2.svg
new file mode 100644
index 0000000..2327567
--- /dev/null
+++ b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/logo.imageset/logo 2.svg
@@ -0,0 +1,36 @@
+
+
+
diff --git a/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/logo.imageset/logo.svg b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/logo.imageset/logo.svg
new file mode 100644
index 0000000..2327567
--- /dev/null
+++ b/examples/gui_swift_ui/SwiftUiExample/Assets.xcassets/logo.imageset/logo.svg
@@ -0,0 +1,36 @@
+
+
+
diff --git a/examples/gui_swift_ui/SwiftUiExample/ContentView.swift b/examples/gui_swift_ui/SwiftUiExample/ContentView.swift
new file mode 100644
index 0000000..892d143
--- /dev/null
+++ b/examples/gui_swift_ui/SwiftUiExample/ContentView.swift
@@ -0,0 +1,128 @@
+//
+// ContentView.swift
+// SwiftUiExample
+//
+
+import SwiftUI
+
+struct ContentView: View {
+ private var deviceModel = DeviceModel()
+ @State private var port: String = ""
+
+ var body: some View {
+ VStack {
+ HStack {
+ Image("logo")
+ .resizable()
+ .aspectRatio(contentMode: .fit)
+ .frame(height: 100)
+ .padding()
+ VStack(alignment: .leading) {
+ HStack{
+ Text("Status: ").bold()
+ Text(deviceModel.isConnected ? "Connected" : "Not Connected")
+ }
+ HStack{
+ Text("Device: ").bold()
+ Text(deviceModel.name ?? "")
+ }
+ HStack {
+ Text("Device ID: ").bold()
+ Text(deviceModel.deviceId ?? "")
+ }
+ HStack {
+ Text("Serial #: ").bold()
+ Text(deviceModel.serialNumber ?? "")
+ }
+ HStack {
+ Text("Firmware Version: ").bold()
+ Text(deviceModel.firmwareVersion ?? "")
+ }
+ }
+ }
+ HStack(alignment: .top) {
+ VStack(alignment: .leading) {
+ TextField("Enter Serial Port...", text: $port)
+ .textFieldStyle(RoundedBorderTextFieldStyle())
+ .disabled(deviceModel.isConnected)
+ Button() {
+ Task {
+ await deviceModel.connect($port.wrappedValue)
+ }
+ } label: {
+ Text("Connect to Stage")
+ .frame(maxWidth: .infinity)
+ }
+ .disabled(deviceModel.isConnected)
+ LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 8) {
+ Button() {
+ Task {
+ await deviceModel.home()
+ }
+ } label: {
+ Text("Home")
+ .frame(maxWidth: .infinity)
+ }
+ Button() {
+ Task {
+ await deviceModel.moveMin()
+ }
+ } label: {
+ Text("Move Min")
+ .frame(maxWidth: .infinity)
+ }
+ Button() {
+ Task {
+ await deviceModel.moveMax()
+ }
+ } label: {
+ Text("Move Max")
+ .frame(maxWidth: .infinity)
+ }
+ Button() {
+ Task {
+ await deviceModel.stop()
+ }
+ } label: {
+ Text("Stop")
+ .frame(maxWidth: .infinity)
+ }
+ }
+ .disabled(!deviceModel.isConnected)
+ }
+ .padding()
+ .frame(maxWidth: .infinity)
+ VStack(alignment: .leading) {
+ Text("Position: ")
+ .font(.title)
+ .bold()
+ HStack {
+ Text(deviceModel.position ?? "?")
+ .font(.title2)
+ .frame(minWidth: 80, alignment: .leading)
+ Text("mm")
+ .font(.title2)
+ }
+
+ }
+ .frame(maxWidth: .infinity)
+ }
+ HStack {
+ if deviceModel.error != nil {
+ Image(systemName: "exclamationmark.circle.fill")
+ .resizable()
+ .aspectRatio(contentMode: .fit)
+ .frame(width: 20)
+ .foregroundColor(Color(.systemRed))
+ Text(deviceModel.error!)
+ }
+ }
+ .frame(minHeight: 20)
+ }
+ .padding()
+ }
+}
+
+#Preview {
+ ContentView()
+}
diff --git a/examples/gui_swift_ui/SwiftUiExample/Models/DeviceModel.swift b/examples/gui_swift_ui/SwiftUiExample/Models/DeviceModel.swift
new file mode 100644
index 0000000..26e1423
--- /dev/null
+++ b/examples/gui_swift_ui/SwiftUiExample/Models/DeviceModel.swift
@@ -0,0 +1,121 @@
+//
+// DeviceModel.swift
+// SwiftUiExample
+//
+
+import Foundation
+import Combine
+import SwiftUI
+
+import ZaberMotion
+import ZaberMotionAscii
+import ZaberMotionExceptions
+
+enum AppError: Error {
+ case notConnectedError(String)
+}
+
+enum DeviceConstants {
+ static let deviceAddress = 1
+ static let axisNumber = 1
+}
+
+@MainActor
+@Observable
+final class DeviceModel: Sendable {
+ var connection: Connection?
+
+ var isConnected: Bool{
+ get {
+ return connection != nil
+ }
+ }
+
+ var device: Device {
+ get throws {
+ guard let device = try connection?.getDevice(deviceAddress: DeviceConstants.deviceAddress) else {
+ throw AppError.notConnectedError("Not connected!")
+ }
+ return device
+ }
+ }
+
+ var axis: ZaberMotionAscii.Axis {
+ get throws {
+ return try device.getAxis(axisNumber: DeviceConstants.axisNumber)
+ }
+ }
+
+ var name: String?
+ var deviceId: String?
+ var serialNumber: String?
+ var firmwareVersion: String?
+ var position: String?
+ var error: String?
+
+ func connect(_ port: String) async -> Void {
+ await tryCommand {
+ connection = try await Connection.openSerialPort(portName: port)
+
+ _ = try await device.identify()
+
+ try populateIdentity()
+
+ Task {
+ while true {
+ do {
+ position = String(format: "%.3f", try await axis.getPosition(unit: Units.Length.mm))
+ try? await Task.sleep(nanoseconds: 100_000_000) // 100ms
+ } catch let e as MotionLibException {
+ position = nil
+ error = e.toString()
+ try? await Task.sleep(nanoseconds: 1000_000_000) // 1000ms
+ }
+ }
+ }
+ }
+ }
+
+ func home() async -> Void {
+ await tryCommand {
+ try await axis.home(waitUntilIdle: false)
+ }
+ }
+
+ func moveMin() async -> Void {
+ await tryCommand {
+ try await axis.moveMin(waitUntilIdle: false)
+ }
+ }
+
+ func moveMax() async -> Void {
+ await tryCommand {
+ try await axis.moveMax(waitUntilIdle: false)
+ }
+ }
+
+ func stop() async -> Void {
+ await tryCommand {
+ try await axis.stop(waitUntilIdle: false)
+ }
+ }
+
+ private func tryCommand(_ command: () async throws -> Void) async {
+ do {
+ try await command()
+ self.error = nil
+ } catch let e as MotionLibException {
+ self.error = e.toString()
+ } catch {
+ self.error = "Error: \(error)"
+ }
+ }
+
+ private func populateIdentity() throws -> Void {
+ let identity = try device.identity
+ name = identity.name
+ deviceId = String(identity.deviceId)
+ serialNumber = String(identity.serialNumber)
+ firmwareVersion = "\(identity.firmwareVersion.major).\(identity.firmwareVersion.minor)"
+ }
+}
diff --git a/examples/gui_swift_ui/SwiftUiExample/Preview Content/Preview Assets.xcassets/Contents.json b/examples/gui_swift_ui/SwiftUiExample/Preview Content/Preview Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/examples/gui_swift_ui/SwiftUiExample/Preview Content/Preview Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/examples/gui_swift_ui/SwiftUiExample/SwiftUiExample.entitlements b/examples/gui_swift_ui/SwiftUiExample/SwiftUiExample.entitlements
new file mode 100644
index 0000000..0c67376
--- /dev/null
+++ b/examples/gui_swift_ui/SwiftUiExample/SwiftUiExample.entitlements
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/examples/gui_swift_ui/SwiftUiExample/SwiftUiExampleApp.swift b/examples/gui_swift_ui/SwiftUiExample/SwiftUiExampleApp.swift
new file mode 100644
index 0000000..f482fd0
--- /dev/null
+++ b/examples/gui_swift_ui/SwiftUiExample/SwiftUiExampleApp.swift
@@ -0,0 +1,15 @@
+//
+// SwiftUiExampleApp.swift
+// SwiftUiExample
+//
+
+import SwiftUI
+
+@main
+struct SwiftUiExampleApp: App {
+ var body: some Scene {
+ WindowGroup {
+ ContentView()
+ }
+ }
+}
diff --git a/examples/gui_swift_ui/article.yml b/examples/gui_swift_ui/article.yml
new file mode 100644
index 0000000..de15dbd
--- /dev/null
+++ b/examples/gui_swift_ui/article.yml
@@ -0,0 +1,9 @@
+authors:
+ - Silviu Toderita
+date: 2025-04-21
+category: Graphical User Interface
+tags:
+ - Swift
+ - SwiftUI
+ - MacOS
+picture: img/screenshot.png
diff --git a/examples/gui_swift_ui/img/screenshot.png b/examples/gui_swift_ui/img/screenshot.png
new file mode 100644
index 0000000..81a58bd
Binary files /dev/null and b/examples/gui_swift_ui/img/screenshot.png differ