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. + +Screenshot + +## 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