diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..b9661ce
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,15 @@
+---
+BasedOnStyle: Chromium
+AlignTrailingComments: true
+BreakBeforeBraces: Linux
+ColumnLimit: 120
+IndentWidth: 4
+KeepEmptyLinesAtTheStartOfBlocks: false
+MaxEmptyLinesToKeep: 2
+ObjCSpaceAfterProperty: true
+ObjCSpaceBeforeProtocolList: true
+PointerBindsToType: false
+SpacesBeforeTrailingComments: 1
+TabWidth: 8
+UseTab: Never
+...
diff --git a/.gitignore b/.gitignore
index a74cfed..96c6c72 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,3 +52,7 @@ Carthage/Build
fastlane/report.xml
fastlane/screenshots
+
+# Swift Package Manager
+.swiftpm/
+.build/
diff --git a/MathEditor.podspec b/MathEditor.podspec
deleted file mode 100644
index 6e742b4..0000000
--- a/MathEditor.podspec
+++ /dev/null
@@ -1,23 +0,0 @@
-Pod::Spec.new do |s|
- s.name = "MathEditor"
- s.version = "0.3.0"
- s.summary = "An editor for editing math equations."
- s.description = <<-DESC
-MathEditor provides a WYSIWYG editor for math equations. It comes with a
-math keyboard that is included with the library, however you can provide
-your own keyboard. It uses iosMath to render the formulae using latex
-typesetting rules.
- DESC
- s.homepage = "https://github.com/kostub/MathEditor"
- s.license = { :type => "MIT", :file => "LICENSE" }
- s.author = { "Kostub Deshmukh" => "kostub@gmail.com" }
- s.source = { :git => "https://github.com/kostub/MathEditor.git", :tag => s.version.to_s }
- s.ios.deployment_target = '8.0'
- s.source_files = 'mathEditor/**/*'
- s.private_header_files = 'mathEditor/internal/**/*.h'
- s.resource_bundles = {
- 'MTKeyboardResources' => 'MathKeyboardResources/**/*'
- }
- s.frameworks = 'UIKit'
- s.dependency 'iosMath', '~> 0.9.3'
-end
diff --git a/MathEditor.xcworkspace/contents.xcworkspacedata b/MathEditor.xcworkspace/contents.xcworkspacedata
deleted file mode 100644
index 152723e..0000000
--- a/MathEditor.xcworkspace/contents.xcworkspacedata
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
diff --git a/MathEditor.xcodeproj/project.pbxproj b/MathEditorObjC/MathEditor.xcodeproj/project.pbxproj
similarity index 74%
rename from MathEditor.xcodeproj/project.pbxproj
rename to MathEditorObjC/MathEditor.xcodeproj/project.pbxproj
index e601607..a86b507 100644
--- a/MathEditor.xcodeproj/project.pbxproj
+++ b/MathEditorObjC/MathEditor.xcodeproj/project.pbxproj
@@ -3,13 +3,10 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 47;
+ objectVersion = 60;
objects = {
/* Begin PBXBuildFile section */
- 1385A98064E559BF2F757304 /* libPods-MathEditor_Example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8FF6761D7D7AC0EC9F0679C9 /* libPods-MathEditor_Example.a */; };
- 2040F7C6A902C4AEF598B076 /* libPods-MathEditor.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 473720D7C87DFE272F052861 /* libPods-MathEditor.a */; };
- 243FB02EE755A1D41861623B /* libPods-MathEditor_Tests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 023479B03FDA39233191C9B7 /* libPods-MathEditor_Tests.a */; };
490BE5781CE6A08100AE31A0 /* MTDisplayEditingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 490BE5771CE6A08100AE31A0 /* MTDisplayEditingTest.m */; };
49DEC8241CF5523E000053CD /* MTCaretView.m in Sources */ = {isa = PBXBuildFile; fileRef = 49DEC81F1CF5523E000053CD /* MTCaretView.m */; };
49DEC8251CF5523E000053CD /* MTDisplay+Editing.m in Sources */ = {isa = PBXBuildFile; fileRef = 49DEC8211CF5523E000053CD /* MTDisplay+Editing.m */; };
@@ -29,6 +26,8 @@
6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; };
+ 862D28DD2E2D4C4400F9B6FE /* MathEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 862D28DC2E2D4C4400F9B6FE /* MathEditor */; };
+ 86B937A02F75C33F00FFC54B /* MathKeyboard in Frameworks */ = {isa = PBXBuildFile; productRef = 86B9379F2F75C33F00FFC54B /* MathKeyboard */; };
873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */; };
/* End PBXBuildFile section */
@@ -45,10 +44,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
- 023479B03FDA39233191C9B7 /* libPods-MathEditor_Tests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MathEditor_Tests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
09D14643E1EDE3F6786E897E /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
- 1377C04C01CE56EB11AB5A1B /* Pods-MathEditor.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MathEditor.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MathEditor/Pods-MathEditor.debug.xcconfig"; sourceTree = ""; };
- 473720D7C87DFE272F052861 /* libPods-MathEditor.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MathEditor.a"; sourceTree = BUILT_PRODUCTS_DIR; };
490BE5561CE695CC00AE31A0 /* libMathEditor.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libMathEditor.a; sourceTree = BUILT_PRODUCTS_DIR; };
490BE5771CE6A08100AE31A0 /* MTDisplayEditingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTDisplayEditingTest.m; sourceTree = ""; };
497F92211CF8CBFF00022162 /* MathEditor-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MathEditor-Prefix.pch"; sourceTree = ""; };
@@ -74,7 +70,6 @@
49DEC83D1CF5591D000053CD /* WhiteBGKeyboardTab.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = WhiteBGKeyboardTab.xcassets; sourceTree = ""; };
49DEC83E1CF57D75000053CD /* lmroman10-bolditalic.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "lmroman10-bolditalic.otf"; sourceTree = ""; };
49DEC83F1CF59F82000053CD /* MathEditor.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = MathEditor.podspec; sourceTree = ""; };
- 5628D2459CDCF8D3064ABC73 /* libPods-iosMathEditor.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-iosMathEditor.a"; sourceTree = BUILT_PRODUCTS_DIR; };
6003F58A195388D20070C39A /* MathEditor_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MathEditor_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
@@ -92,16 +87,8 @@
6003F5B7195388D20070C39A /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = ""; };
6003F5B9195388D20070C39A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; };
606FC2411953D9B200FFA9A0 /* Tests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tests-Prefix.pch"; sourceTree = ""; };
- 733BAF45E72F86EDFEAAAD61 /* libPods-iosMathEditor_Example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-iosMathEditor_Example.a"; sourceTree = BUILT_PRODUCTS_DIR; };
753F7D8158AE6B036B248A96 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = LICENSE; sourceTree = ""; };
- 7A378091F23FA26A8EEA2F82 /* Pods-MathEditor_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MathEditor_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-MathEditor_Example/Pods-MathEditor_Example.release.xcconfig"; sourceTree = ""; };
873B8AEA1B1F5CCA007FD442 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; };
- 8A93C35286D2F42BBFFF6281 /* Pods-MathEditor.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MathEditor.release.xcconfig"; path = "Pods/Target Support Files/Pods-MathEditor/Pods-MathEditor.release.xcconfig"; sourceTree = ""; };
- 8FF6761D7D7AC0EC9F0679C9 /* libPods-MathEditor_Example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MathEditor_Example.a"; sourceTree = BUILT_PRODUCTS_DIR; };
- A4EC35D984BA58883006D2C3 /* Pods-MathEditor_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MathEditor_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-MathEditor_Tests/Pods-MathEditor_Tests.release.xcconfig"; sourceTree = ""; };
- AA22ED6294D4E5576093F8A8 /* Pods-MathEditor_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MathEditor_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MathEditor_Tests/Pods-MathEditor_Tests.debug.xcconfig"; sourceTree = ""; };
- AEA5AA8AF2C91D3A7B9B05F9 /* libPods-iosMathEditor_Tests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-iosMathEditor_Tests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
- C22335FDD0B9F56B94B8187A /* Pods-MathEditor_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MathEditor_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MathEditor_Example/Pods-MathEditor_Example.debug.xcconfig"; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -109,7 +96,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 2040F7C6A902C4AEF598B076 /* libPods-MathEditor.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -120,7 +106,8 @@
6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */,
6003F592195388D20070C39A /* UIKit.framework in Frameworks */,
6003F58E195388D20070C39A /* Foundation.framework in Frameworks */,
- 1385A98064E559BF2F757304 /* libPods-MathEditor_Example.a in Frameworks */,
+ 86B937A02F75C33F00FFC54B /* MathKeyboard in Frameworks */,
+ 862D28DD2E2D4C4400F9B6FE /* MathEditor in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -131,7 +118,6 @@
6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */,
6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */,
6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */,
- 243FB02EE755A1D41861623B /* libPods-MathEditor_Tests.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -199,19 +185,6 @@
path = ../MathKeyboardResources;
sourceTree = "";
};
- 4D5F9F41E950A502FB2B3BEA /* Pods */ = {
- isa = PBXGroup;
- children = (
- 1377C04C01CE56EB11AB5A1B /* Pods-MathEditor.debug.xcconfig */,
- 8A93C35286D2F42BBFFF6281 /* Pods-MathEditor.release.xcconfig */,
- C22335FDD0B9F56B94B8187A /* Pods-MathEditor_Example.debug.xcconfig */,
- 7A378091F23FA26A8EEA2F82 /* Pods-MathEditor_Example.release.xcconfig */,
- AA22ED6294D4E5576093F8A8 /* Pods-MathEditor_Tests.debug.xcconfig */,
- A4EC35D984BA58883006D2C3 /* Pods-MathEditor_Tests.release.xcconfig */,
- );
- name = Pods;
- sourceTree = "";
- };
6003F581195388D10070C39A = {
isa = PBXGroup;
children = (
@@ -221,7 +194,6 @@
490BE5571CE695CC00AE31A0 /* mathEditor */,
6003F58C195388D20070C39A /* Frameworks */,
6003F58B195388D20070C39A /* Products */,
- 4D5F9F41E950A502FB2B3BEA /* Pods */,
);
sourceTree = "";
};
@@ -242,12 +214,6 @@
6003F58F195388D20070C39A /* CoreGraphics.framework */,
6003F591195388D20070C39A /* UIKit.framework */,
6003F5AF195388D20070C39A /* XCTest.framework */,
- 5628D2459CDCF8D3064ABC73 /* libPods-iosMathEditor.a */,
- 733BAF45E72F86EDFEAAAD61 /* libPods-iosMathEditor_Example.a */,
- AEA5AA8AF2C91D3A7B9B05F9 /* libPods-iosMathEditor_Tests.a */,
- 473720D7C87DFE272F052861 /* libPods-MathEditor.a */,
- 8FF6761D7D7AC0EC9F0679C9 /* libPods-MathEditor_Example.a */,
- 023479B03FDA39233191C9B7 /* libPods-MathEditor_Tests.a */,
);
name = Frameworks;
sourceTree = "";
@@ -313,11 +279,9 @@
isa = PBXNativeTarget;
buildConfigurationList = 490BE55E1CE695CC00AE31A0 /* Build configuration list for PBXNativeTarget "MathEditor" */;
buildPhases = (
- 48A9CBFCAB5894F9BFC552E4 /* [CP] Check Pods Manifest.lock */,
490BE5521CE695CC00AE31A0 /* Sources */,
490BE5531CE695CC00AE31A0 /* Frameworks */,
490BE5541CE695CC00AE31A0 /* CopyFiles */,
- 4C3A2645A6B5FE31FA662BF1 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -332,12 +296,9 @@
isa = PBXNativeTarget;
buildConfigurationList = 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "MathEditor_Example" */;
buildPhases = (
- A3E2D0D1A985C3FEA5864005 /* [CP] Check Pods Manifest.lock */,
6003F586195388D20070C39A /* Sources */,
6003F587195388D20070C39A /* Frameworks */,
6003F588195388D20070C39A /* Resources */,
- D8A5CFAD5F09AAA428AB4D10 /* [CP] Embed Pods Frameworks */,
- E202BD0EA2A01E97CAF04F2E /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -352,12 +313,9 @@
isa = PBXNativeTarget;
buildConfigurationList = 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "MathEditor_Tests" */;
buildPhases = (
- 4B44B263AD3BCE039EE8DD71 /* [CP] Check Pods Manifest.lock */,
6003F5AA195388D20070C39A /* Sources */,
6003F5AB195388D20070C39A /* Frameworks */,
6003F5AC195388D20070C39A /* Resources */,
- E5084554A0ECA0F3755B8CC7 /* [CP] Embed Pods Frameworks */,
- 02495CB9EE6CCCBF5814B5C0 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -374,8 +332,9 @@
6003F582195388D10070C39A /* Project object */ = {
isa = PBXProject;
attributes = {
+ BuildIndependentTargetsInParallel = YES;
CLASSPREFIX = MT;
- LastUpgradeCheck = 0720;
+ LastUpgradeCheck = 2630;
ORGANIZATIONNAME = "Kostub Deshmukh";
TargetAttributes = {
490BE5551CE695CC00AE31A0 = {
@@ -385,13 +344,16 @@
};
buildConfigurationList = 6003F585195388D10070C39A /* Build configuration list for PBXProject "MathEditor" */;
compatibilityVersion = "Xcode 6.3";
- developmentRegion = English;
+ developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 6003F581195388D10070C39A;
+ packageReferences = (
+ 864DCE522F7E9E8400C1C14B /* XCLocalSwiftPackageReference "../MathEditorObjC" */,
+ );
productRefGroup = 6003F58B195388D20070C39A /* Products */;
projectDirPath = "";
projectRoot = "";
@@ -424,129 +386,6 @@
};
/* End PBXResourcesBuildPhase section */
-/* Begin PBXShellScriptBuildPhase section */
- 02495CB9EE6CCCBF5814B5C0 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-MathEditor_Tests/Pods-MathEditor_Tests-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 48A9CBFCAB5894F9BFC552E4 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
- showEnvVarsInLog = 0;
- };
- 4B44B263AD3BCE039EE8DD71 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
- showEnvVarsInLog = 0;
- };
- 4C3A2645A6B5FE31FA662BF1 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-MathEditor/Pods-MathEditor-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- A3E2D0D1A985C3FEA5864005 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
- showEnvVarsInLog = 0;
- };
- D8A5CFAD5F09AAA428AB4D10 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-MathEditor_Example/Pods-MathEditor_Example-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- E202BD0EA2A01E97CAF04F2E /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-MathEditor_Example/Pods-MathEditor_Example-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- E5084554A0ECA0F3755B8CC7 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-MathEditor_Tests/Pods-MathEditor_Tests-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
-/* End PBXShellScriptBuildPhase section */
-
/* Begin PBXSourcesBuildPhase section */
490BE5521CE695CC00AE31A0 /* Sources */ = {
isa = PBXSourcesBuildPhase;
@@ -603,13 +442,12 @@
/* Begin XCBuildConfiguration section */
490BE55C1CE695CC00AE31A0 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 1377C04C01CE56EB11AB5A1B /* Pods-MathEditor.debug.xcconfig */;
buildSettings = {
CLANG_WARN_UNREACHABLE_CODE = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_NO_COMMON_BLOCKS = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 9.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -622,14 +460,13 @@
};
490BE55D1CE695CC00AE31A0 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 8A93C35286D2F42BBFFF6281 /* Pods-MathEditor.release.xcconfig */;
buildSettings = {
CLANG_WARN_UNREACHABLE_CODE = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_NO_COMMON_BLOCKS = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 9.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -644,23 +481,39 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = 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_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_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
@@ -673,9 +526,10 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@@ -684,30 +538,47 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = 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_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_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
+ 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;
- IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
SDKROOT = iphoneos;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
@@ -715,11 +586,11 @@
};
6003F5C0195388D20070C39A /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = C22335FDD0B9F56B94B8187A /* Pods-MathEditor_Example.debug.xcconfig */;
buildSettings = {
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "MathEditorExample/MathEditor-Prefix.pch";
INFOPLIST_FILE = "$(SRCROOT)/MathEditorExample/iosMathEditor-Info.plist";
+ IPHONEOS_DEPLOYMENT_TARGET = 12;
PRODUCT_BUNDLE_IDENTIFIER = MathChat.MathEditorExample;
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app;
@@ -728,11 +599,11 @@
};
6003F5C1195388D20070C39A /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 7A378091F23FA26A8EEA2F82 /* Pods-MathEditor_Example.release.xcconfig */;
buildSettings = {
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "MathEditorExample/MathEditor-Prefix.pch";
INFOPLIST_FILE = "$(SRCROOT)/MathEditorExample/iosMathEditor-Info.plist";
+ IPHONEOS_DEPLOYMENT_TARGET = 12;
PRODUCT_BUNDLE_IDENTIFIER = MathChat.MathEditorExample;
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app;
@@ -741,7 +612,6 @@
};
6003F5C3195388D20070C39A /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = AA22ED6294D4E5576093F8A8 /* Pods-MathEditor_Tests.debug.xcconfig */;
buildSettings = {
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch";
@@ -765,7 +635,6 @@
};
6003F5C4195388D20070C39A /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = A4EC35D984BA58883006D2C3 /* Pods-MathEditor_Tests.release.xcconfig */;
buildSettings = {
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch";
@@ -823,6 +692,29 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
+
+/* Begin XCLocalSwiftPackageReference section */
+ 862D28DB2E2D4C4400F9B6FE /* XCLocalSwiftPackageReference "../MathEditor" */ = {
+ isa = XCLocalSwiftPackageReference;
+ relativePath = ../MathEditor;
+ };
+ 864DCE522F7E9E8400C1C14B /* XCLocalSwiftPackageReference "../MathEditorObjC" */ = {
+ isa = XCLocalSwiftPackageReference;
+ relativePath = ../MathEditorObjC;
+ };
+/* End XCLocalSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ 862D28DC2E2D4C4400F9B6FE /* MathEditor */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = MathEditor;
+ };
+ 86B9379F2F75C33F00FFC54B /* MathKeyboard */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 862D28DB2E2D4C4400F9B6FE /* XCLocalSwiftPackageReference "../MathEditor" */;
+ productName = MathKeyboard;
+ };
+/* End XCSwiftPackageProductDependency section */
};
rootObject = 6003F582195388D10070C39A /* Project object */;
}
diff --git a/MathEditor.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/MathEditorObjC/MathEditor.xcodeproj/project.xcworkspace/contents.xcworkspacedata
similarity index 68%
rename from MathEditor.xcodeproj/project.xcworkspace/contents.xcworkspacedata
rename to MathEditorObjC/MathEditor.xcodeproj/project.xcworkspace/contents.xcworkspacedata
index 6228eeb..919434a 100644
--- a/MathEditor.xcodeproj/project.xcworkspace/contents.xcworkspacedata
+++ b/MathEditorObjC/MathEditor.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -2,6 +2,6 @@
+ location = "self:">
diff --git a/MathEditorObjC/MathEditor.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/MathEditorObjC/MathEditor.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
new file mode 100644
index 0000000..1c9ed0b
--- /dev/null
+++ b/MathEditorObjC/MathEditor.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -0,0 +1,15 @@
+{
+ "originHash" : "2d65c5df9e449df855f0e8588f2ee81f415d1a455099513dd2c4e8b029567c28",
+ "pins" : [
+ {
+ "identity" : "iosmath",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/maitbayev/iosMath.git",
+ "state" : {
+ "branch" : "master",
+ "revision" : "bf4a5466fc405031977f2edcf806ccb119c23836"
+ }
+ }
+ ],
+ "version" : 3
+}
diff --git a/MathEditor.xcodeproj/xcshareddata/xcschemes/iosMathEditor-Example.xcscheme b/MathEditorObjC/MathEditor.xcodeproj/xcshareddata/xcschemes/iosMathEditor-Example.xcscheme
similarity index 96%
rename from MathEditor.xcodeproj/xcshareddata/xcschemes/iosMathEditor-Example.xcscheme
rename to MathEditorObjC/MathEditor.xcodeproj/xcshareddata/xcschemes/iosMathEditor-Example.xcscheme
index 6ca118a..f7dfae5 100644
--- a/MathEditor.xcodeproj/xcshareddata/xcschemes/iosMathEditor-Example.xcscheme
+++ b/MathEditorObjC/MathEditor.xcodeproj/xcshareddata/xcschemes/iosMathEditor-Example.xcscheme
@@ -1,6 +1,6 @@
+
+
+
+
@@ -39,17 +48,6 @@
-
-
-
-
-
-
-
-
diff --git a/MathEditorExample/Main.storyboard b/MathEditorObjC/MathEditorExample/Main.storyboard
similarity index 100%
rename from MathEditorExample/Main.storyboard
rename to MathEditorObjC/MathEditorExample/Main.storyboard
diff --git a/MathEditorExample/MathEditor-Prefix.pch b/MathEditorObjC/MathEditorExample/MathEditor-Prefix.pch
similarity index 100%
rename from MathEditorExample/MathEditor-Prefix.pch
rename to MathEditorObjC/MathEditorExample/MathEditor-Prefix.pch
diff --git a/MathEditorExample/en.lproj/InfoPlist.strings b/MathEditorObjC/MathEditorExample/en.lproj/InfoPlist.strings
similarity index 100%
rename from MathEditorExample/en.lproj/InfoPlist.strings
rename to MathEditorObjC/MathEditorExample/en.lproj/InfoPlist.strings
diff --git a/MathEditorExample/iosMathEditor-Info.plist b/MathEditorObjC/MathEditorExample/iosMathEditor-Info.plist
similarity index 100%
rename from MathEditorExample/iosMathEditor-Info.plist
rename to MathEditorObjC/MathEditorExample/iosMathEditor-Info.plist
diff --git a/MathEditorExample/main.m b/MathEditorObjC/MathEditorExample/main.m
similarity index 100%
rename from MathEditorExample/main.m
rename to MathEditorObjC/MathEditorExample/main.m
diff --git a/MathEditorObjC/Package.resolved b/MathEditorObjC/Package.resolved
new file mode 100644
index 0000000..24b9309
--- /dev/null
+++ b/MathEditorObjC/Package.resolved
@@ -0,0 +1,14 @@
+{
+ "pins" : [
+ {
+ "identity" : "iosmath",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/maitbayev/iosMath.git",
+ "state" : {
+ "branch" : "master",
+ "revision" : "bf4a5466fc405031977f2edcf806ccb119c23836"
+ }
+ }
+ ],
+ "version" : 2
+}
diff --git a/MathEditorObjC/Package.swift b/MathEditorObjC/Package.swift
new file mode 100644
index 0000000..8b4ac2b
--- /dev/null
+++ b/MathEditorObjC/Package.swift
@@ -0,0 +1,52 @@
+// swift-tools-version: 5.6
+
+import PackageDescription
+
+let package = Package(
+ name: "MathEditor",
+ defaultLocalization: "en",
+ platforms: [.iOS(.v13), .macOS(.v11)],
+ products: [
+ .library(
+ name: "MathEditor",
+ targets: ["MathEditor"]),
+ .library(
+ name: "MathKeyboard",
+ targets: ["MathKeyboard"]),
+ ],
+ dependencies: [
+ .package(url: "https://github.com/maitbayev/iosMath.git", branch: "master")
+ ],
+ targets: [
+ .target(
+ name: "MathEditor",
+ dependencies: [.product(name: "iosMath", package: "iosMath")],
+ path: "./mathEditor",
+ cSettings: [
+ .headerSearchPath("./editor"),
+ .headerSearchPath("./internal"),
+ .headerSearchPath("./include"),
+ ]
+ ),
+ .target(
+ name: "MathKeyboard",
+ dependencies: [.product(name: "iosMath", package: "iosMath"), "MathEditor"],
+ path: "./mathKeyboard",
+ resources: [.process("MathKeyboardResources")],
+ cSettings: [
+ .headerSearchPath("./keyboard")
+ ]
+ ),
+ .testTarget(
+ name: "MathEditorTests",
+ dependencies: ["MathEditor", "iosMath"],
+ path: "./Tests",
+ cSettings: [
+ .headerSearchPath("../mathEditor/editor"),
+ .headerSearchPath("../mathEditor/keyboard"),
+ .headerSearchPath("../mathEditor/internal"),
+ .headerSearchPath("../mathEditor/include"),
+ ]
+ ),
+ ]
+)
diff --git a/Tests/MTDisplayEditingTest.m b/MathEditorObjC/Tests/MTDisplayEditingTest.m
similarity index 98%
rename from Tests/MTDisplayEditingTest.m
rename to MathEditorObjC/Tests/MTDisplayEditingTest.m
index 23adc74..ffd35f2 100644
--- a/Tests/MTDisplayEditingTest.m
+++ b/MathEditorObjC/Tests/MTDisplayEditingTest.m
@@ -17,7 +17,6 @@
#import "MTMathListBuilder.h"
#import "MTFontManager.h"
#import "MTTypesetter.h"
-
@interface MTDisplayEditingTest : XCTestCase
@property (nonatomic) MTFont* font;
@@ -41,7 +40,8 @@ - (void)tearDown
}
static NSValue* point(CGFloat x, CGFloat y) {
- return [NSValue valueWithCGPoint:CGPointMake(x, y)];
+ CGPoint cgPoint = CGPointMake(x, y);
+ return [NSValue value:&cgPoint withObjCType:@encode(CGPoint)];
}
- (void)testClosestPointForExpression:(NSString*) expr data:(NSDictionary*) testData
@@ -50,7 +50,8 @@ - (void)testClosestPointForExpression:(NSString*) expr data:(NSDictionary*) test
MTDisplay* displayList = [MTTypesetter createLineForMathList:ml font:_font style:kMTLineStyleDisplay];
for (NSValue* point in testData) {
- CGPoint cgPoint = [point CGPointValue];
+ CGPoint cgPoint;
+ [point getValue:&cgPoint];
MTMathListIndex* expectedIndex = testData[point];
MTMathListIndex* index = [displayList closestIndexToPoint:cgPoint];
XCTAssertEqualObjects(index, expectedIndex, @"Index %@ does not match %@ for point (%f, %f)", index, expectedIndex, cgPoint.x, cgPoint.y);
diff --git a/Tests/Tests-Info.plist b/MathEditorObjC/Tests/Tests-Info.plist
similarity index 100%
rename from Tests/Tests-Info.plist
rename to MathEditorObjC/Tests/Tests-Info.plist
diff --git a/Tests/Tests-Prefix.pch b/MathEditorObjC/Tests/Tests-Prefix.pch
similarity index 100%
rename from Tests/Tests-Prefix.pch
rename to MathEditorObjC/Tests/Tests-Prefix.pch
diff --git a/Tests/en.lproj/InfoPlist.strings b/MathEditorObjC/Tests/en.lproj/InfoPlist.strings
similarity index 100%
rename from Tests/en.lproj/InfoPlist.strings
rename to MathEditorObjC/Tests/en.lproj/InfoPlist.strings
diff --git a/MathEditorObjC/mathEditor/editor/MTEditableMathLabel+NSView.m b/MathEditorObjC/mathEditor/editor/MTEditableMathLabel+NSView.m
new file mode 100644
index 0000000..9bfa91e
--- /dev/null
+++ b/MathEditorObjC/mathEditor/editor/MTEditableMathLabel+NSView.m
@@ -0,0 +1,42 @@
+//
+// MTEditableMathLabel+NSView.h
+// MathEditor
+//
+// Created by Madiyar Aitbayev on 20/03/2026.
+//
+
+#if TARGET_OS_OSX
+
+#import "MTEditableMathLabel.h"
+#import "MTMathUILabel.h"
+#import "MTView/MTView+HitTest.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface MTEditableMathLabel (NSView)
+@end
+
+@interface MTEditableMathLabel ()
+@property (nonatomic) MTMathUILabel *label;
+@end
+
+@implementation MTEditableMathLabel(NSView)
+
+- (void)layout {
+ [super layout];
+ [self doLayout];
+}
+
+- (BOOL)isFlipped {
+ return YES;
+}
+
+- (nullable NSView *)hitTest:(NSPoint)point {
+ return [self hitTestOutsideBounds:point ignoringSubviews:@[self.label]];
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif // TARGET_OS_OSX
diff --git a/MathEditorObjC/mathEditor/editor/MTEditableMathLabel+Responder.m b/MathEditorObjC/mathEditor/editor/MTEditableMathLabel+Responder.m
new file mode 100644
index 0000000..0e3e4d7
--- /dev/null
+++ b/MathEditorObjC/mathEditor/editor/MTEditableMathLabel+Responder.m
@@ -0,0 +1,90 @@
+//
+// MTEditableMathLabel+UIResponder.h
+// MathEditor
+//
+// Created by Madiyar Aitbayev on 20/03/2026.
+//
+
+#import
+#import "MTConfig.h"
+#import "MTEditableMathLabel.h"
+#import "MTView/MTView+FirstResponder.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface MTEditableMathLabel (Responder)
+@end
+
+@implementation MTEditableMathLabel (Responder)
+
+#if TARGET_OS_IPHONE
+
+- (nullable UIView *)inputView
+{
+ return self.keyboard;
+}
+
+#endif // TARGET_OS_IPHONE
+
+/**
+ NSResponder protocol override.
+ Our view can become first responder to receive user text input.
+ */
+- (BOOL)acceptsFirstResponder {
+ return [self canBecomeFirstResponder];
+}
+
+/**
+ UIResponder protocol override.
+ Our view can become first responder to receive user text input.
+ */
+- (BOOL)canBecomeFirstResponder
+{
+ return YES;
+}
+
+- (BOOL)becomeFirstResponder
+{
+ BOOL canBecome = [super becomeFirstResponder];
+ if (canBecome) {
+ [self doBecomeFirstResponder];
+ } else {
+ // Sometimes it takes some time
+ // [self performSelector:@selector(startEditing) withObject:nil afterDelay:0.0];
+ }
+ return canBecome;
+}
+
+/**
+ UIResponder protocol override.
+ Called when our view is being asked to resign first responder state.
+ */
+- (BOOL)resignFirstResponder
+{
+ BOOL val = YES;
+ if ([self isFirstResponder]) {
+ val = [super resignFirstResponder];
+ [self doResignFirstResponder];
+ }
+ return val;
+}
+
+
+#if TARGET_OS_OSX
+
+- (void)keyDown:(NSEvent *)event {
+ // interpretKeyEvents feeds the event into the input system,
+ // which calls insertText: or deleteBackward: as appropriate.
+ [self interpretKeyEvents:@[event]];
+}
+
+- (void)deleteBackward:(nullable id)sender {
+ [self deleteBackward];
+}
+
+#endif // TARGET_OS_OSX
+
+@end
+
+NS_ASSUME_NONNULL_END
+
diff --git a/MathEditorObjC/mathEditor/editor/MTEditableMathLabel+UITextInput.m b/MathEditorObjC/mathEditor/editor/MTEditableMathLabel+UITextInput.m
new file mode 100644
index 0000000..e9f2dfc
--- /dev/null
+++ b/MathEditorObjC/mathEditor/editor/MTEditableMathLabel+UITextInput.m
@@ -0,0 +1,226 @@
+//
+// NSObject+MTEditableMathLabel_UITextInput.h
+// MathEditor
+//
+// Created by Madiyar Aitbayev on 20/03/2026.
+//
+
+#if TARGET_OS_IPHONE
+
+#import
+#import
+#import "MTEditableMathLabel.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface MTEditableMathLabel (UITextInput)
+@end
+
+@implementation MTEditableMathLabel (UITextInput)
+
+// These are blank just to get a UITextInput implementation, to fix the dictation button bug.
+// Proposed fix from: http://stackoverflow.com/questions/20980898/work-around-for-dictation-custom-text-view-bug
+
+//@synthesize beginningOfDocument;@
+//@synthesize endOfDocument;
+//@synthesize inputDelegate;
+//@synthesize markedTextRange;
+//@synthesize markedTextStyle;
+//@synthesize selectedTextRange;
+//@synthesize tokenizer;
+
+- (nullable UITextRange *)selectedTextRange
+{
+ return objc_getAssociatedObject(self, @selector(selectedTextRange));
+}
+- (void)setSelectedTextRange:(nullable UITextRange *)selectedTextRange
+{
+ objc_setAssociatedObject(self, @selector(selectedTextRange), selectedTextRange, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+}
+
+- (nullable id)inputDelegate
+{
+ id (^block)(void) = objc_getAssociatedObject(self, @selector(inputDelegate));
+ return block ? block() : nil;
+}
+
+- (void)setInputDelegate:(nullable id)inputDelegate
+{
+ __weak id weakDelegate = inputDelegate;
+ id (^block)(void) = ^{
+ return weakDelegate;
+ };
+ objc_setAssociatedObject(self, @selector(inputDelegate), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
+}
+
+- (nullable UITextRange *)markedTextRange
+{
+ return objc_getAssociatedObject(self, @selector(markedTextRange));
+}
+
+- (nullable NSDictionary *)markedTextStyle
+{
+ return objc_getAssociatedObject(self, @selector(markedTextStyle));
+}
+- (void)setMarkedTextStyle:(nullable NSDictionary *)markedTextStyle
+{
+ objc_setAssociatedObject(self, @selector(markedTextStyle), markedTextStyle, OBJC_ASSOCIATION_COPY_NONATOMIC);
+}
+
+- (UITextPosition *)beginningOfDocument
+{
+ return objc_getAssociatedObject(self, @selector(beginningOfDocument));
+}
+
+- (UITextPosition *)endOfDocument
+{
+ return objc_getAssociatedObject(self, @selector(endOfDocument));
+}
+
+- (id)tokenizer
+{
+ id tokenizer = objc_getAssociatedObject(self, @selector(tokenizer));
+ if (!tokenizer) {
+ tokenizer = [[UITextInputStringTokenizer alloc] initWithTextInput:self];
+ objc_setAssociatedObject(self, @selector(tokenizer), tokenizer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+ }
+ return tokenizer;
+}
+
+- (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition *)position
+ inDirection:(UITextStorageDirection)direction
+{
+ return UITextWritingDirectionLeftToRight;
+}
+
+- (CGRect)caretRectForPosition:(UITextPosition *)position
+{
+ return CGRectZero;
+}
+
+- (void)unmarkText
+{
+}
+
+- (nullable UITextRange *)characterRangeAtPoint:(CGPoint)point
+{
+ return nil;
+}
+- (nullable UITextRange *)characterRangeByExtendingPosition:(UITextPosition *)position
+ inDirection:(UITextLayoutDirection)direction
+{
+ return nil;
+}
+- (nullable UITextPosition *)closestPositionToPoint:(CGPoint)point
+{
+ return nil;
+}
+- (nullable UITextPosition *)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange *)range
+{
+ return nil;
+}
+- (NSComparisonResult)comparePosition:(UITextPosition *)position toPosition:(UITextPosition *)other
+{
+ return NSOrderedSame;
+}
+- (void)dictationRecognitionFailed
+{
+}
+- (void)dictationRecordingDidEnd
+{
+}
+- (CGRect)firstRectForRange:(UITextRange *)range
+{
+ return CGRectZero;
+}
+
+- (CGRect)frameForDictationResultPlaceholder:(id)placeholder
+{
+ return CGRectZero;
+}
+- (void)insertDictationResult:(NSArray *)dictationResult
+{
+}
+- (id)insertDictationResultPlaceholder
+{
+ return nil;
+}
+
+- (NSInteger)offsetFromPosition:(UITextPosition *)fromPosition toPosition:(UITextPosition *)toPosition
+{
+ return 0;
+}
+- (nullable UITextPosition *)positionFromPosition:(UITextPosition *)position
+ inDirection:(UITextLayoutDirection)direction
+ offset:(NSInteger)offset
+{
+ return nil;
+}
+- (nullable UITextPosition *)positionFromPosition:(UITextPosition *)position offset:(NSInteger)offset
+{
+ return nil;
+}
+
+- (nullable UITextPosition *)positionWithinRange:(UITextRange *)range
+ farthestInDirection:(UITextLayoutDirection)direction
+{
+ return nil;
+}
+- (void)removeDictationResultPlaceholder:(id)placeholder willInsertResult:(BOOL)willInsertResult
+{
+}
+- (void)replaceRange:(UITextRange *)range withText:(NSString *)text
+{
+}
+- (NSArray *)selectionRectsForRange:(UITextRange *)range
+{
+ return nil;
+}
+- (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection forRange:(UITextRange *)range
+{
+}
+- (void)setMarkedText:(nullable NSString *)markedText selectedRange:(NSRange)selectedRange
+{
+}
+
+- (nullable NSString *)textInRange:(UITextRange *)range
+{
+ return nil;
+}
+- (nullable UITextRange *)textRangeFromPosition:(UITextPosition *)fromPosition toPosition:(UITextPosition *)toPosition
+{
+ return nil;
+}
+
+#pragma mark - UITextInputTraits
+
+- (UITextAutocapitalizationType)autocapitalizationType
+{
+ return UITextAutocapitalizationTypeNone;
+}
+
+- (UITextAutocorrectionType)autocorrectionType
+{
+ return UITextAutocorrectionTypeNo;
+}
+
+- (UIReturnKeyType)returnKeyType
+{
+ return UIReturnKeyDefault;
+}
+
+- (UITextSpellCheckingType)spellCheckingType
+{
+ return UITextSpellCheckingTypeNo;
+}
+
+- (UIKeyboardType)keyboardType
+{
+ return UIKeyboardTypeASCIICapable;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif
diff --git a/MathEditorObjC/mathEditor/editor/MTEditableMathLabel+UIView.m b/MathEditorObjC/mathEditor/editor/MTEditableMathLabel+UIView.m
new file mode 100644
index 0000000..7b539e1
--- /dev/null
+++ b/MathEditorObjC/mathEditor/editor/MTEditableMathLabel+UIView.m
@@ -0,0 +1,40 @@
+//
+// MTEditableMathLabel+NSView.h
+// MathEditor
+//
+// Created by Madiyar Aitbayev on 20/03/2026.
+//
+
+#if TARGET_OS_IPHONE
+
+#import "MTEditableMathLabel.h"
+#import "MTCaretView.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface MTEditableMathLabel (UIView)
+@end
+
+@implementation MTEditableMathLabel(UIView)
+
+-(void)layoutSubviews
+{
+ [super layoutSubviews];
+ [self doLayout];
+}
+
+- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event
+{
+ BOOL inside = [super pointInside:point withEvent:event];
+ if (inside) {
+ return YES;
+ }
+ // check if a point is in the caret view.
+ return [self.caretView pointInside:[self convertPoint:point toView:self.caretView] withEvent:event];
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif // TARGET_OS_IPHONE
diff --git a/mathEditor/editor/MTEditableMathLabel.m b/MathEditorObjC/mathEditor/editor/MTEditableMathLabel.m
similarity index 81%
rename from mathEditor/editor/MTEditableMathLabel.m
rename to MathEditorObjC/mathEditor/editor/MTEditableMathLabel.m
index 05f528f..c0c757b 100644
--- a/mathEditor/editor/MTEditableMathLabel.m
+++ b/MathEditorObjC/mathEditor/editor/MTEditableMathLabel.m
@@ -14,22 +14,25 @@
#import "MTMathList.h"
#import "MTMathUILabel.h"
#import "MTMathAtomFactory.h"
+#import "MTCancelView.h"
#import "MTCaretView.h"
+#import "MTTapGestureRecognizer.h"
#import "MTMathList+Editing.h"
#import "MTDisplay+Editing.h"
+#import "MTView/MTView+AutoLayout.h"
+#import "MTView/MTView+Layout.h"
+#import "MTView/MTView+FirstResponder.h"
#import "MTUnicode.h"
#import "MTMathListBuilder.h"
-@interface MTEditableMathLabel()
+@interface MTEditableMathLabel()
@property (nonatomic) MTMathUILabel* label;
-@property (nonatomic) UITapGestureRecognizer* tapGestureRecognizer;
+@property (nonatomic) MTTapGestureRecognizer* tapGestureRecognizer;
@end
-
@implementation MTEditableMathLabel {
- MTCaretView* _caretView;
MTMathListIndex* _insertionIndex;
CGAffineTransform _flipTransform;
NSMutableArray* _indicesToHighlight;
@@ -53,59 +56,53 @@ - (void)awakeFromNib
- (void) createCancelImage
{
- self.cancelImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"cross"]];
+ if (self.cancelImage != nil) {
+ return;
+ }
+ self.cancelImage = [[MTCancelView alloc] initWithTarget:self action:@selector(clear)];
CGRect frame = CGRectMake(self.frame.size.width - 55, (self.frame.size.height - 45)/2, 45, 45);
self.cancelImage.frame = frame;
[self addSubview:self.cancelImage];
-
- self.cancelImage.userInteractionEnabled = YES;
- UITapGestureRecognizer *cancelRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(clearTapped:)];
- [self.cancelImage addGestureRecognizer:cancelRecognizer];
- cancelRecognizer.delegate = nil;
- self.cancelImage.hidden = YES;
}
- (void) initialize
{
// Add tap gesture recognizer to let the user enter editing mode.
- self.tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
+ self.tapGestureRecognizer = [[MTTapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
[self addGestureRecognizer:self.tapGestureRecognizer];
- self.tapGestureRecognizer.delegate = self;
// Create our text storage.
self.mathList = [MTMathList new];
-
- self.userInteractionEnabled = YES;
- self.autoresizesSubviews = YES;
-
+
// Create and set up the APLSimpleCoreTextView that will do the drawing.
MTMathUILabel *label = [[MTMathUILabel alloc] initWithFrame:self.bounds];
- label.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self addSubview:label];
+ [label pinToSuperview];
label.fontSize = 30;
label.backgroundColor = self.backgroundColor;
+
+ #if TARGET_OS_IPHONE
label.userInteractionEnabled = NO;
+ #endif
label.textAlignment = kMTTextAlignmentCenter;
self.label = label;
+ // [self createCancelImage];
CGAffineTransform transform = CGAffineTransformMakeTranslation(0, self.bounds.size.height);
_flipTransform = CGAffineTransformConcat(CGAffineTransformMakeScale(1.0, -1.0), transform);
_caretView = [[MTCaretView alloc] initWithEditor:self];
- _caretView.caretColor = [UIColor colorWithWhite:0.1 alpha:1.0];
+ _caretView.caretColor = [MTColor colorWithWhite:0.1 alpha:1.0];
_indicesToHighlight = [NSMutableArray array];
- _highlightColor = [UIColor colorWithRed:0.8 green:0 blue:0.0 alpha:1.0];
+ _highlightColor = [MTColor colorWithRed:0.8 green:0 blue:0.0 alpha:1.0];
[self bringSubviewToFront:self.cancelImage];
// start with an empty math list
self.mathList = [MTMathList new];
}
--(void)layoutSubviews
-{
- [super layoutSubviews];
-
+-(void)doLayout {
CGRect frame = CGRectMake(self.frame.size.width - 55, (self.frame.size.height - 45)/2, 45, 45);
self.cancelImage.frame = frame;
@@ -117,7 +114,27 @@ -(void)layoutSubviews
[self insertionPointChanged];
}
-- (void)setBackgroundColor:(UIColor *)backgroundColor
+- (void)setTextColor:(MTColor *)textColor
+{
+ self.label.textColor = textColor;
+}
+
+- (MTColor*)textColor
+{
+ return self.label.textColor;
+}
+
+- (void)setCaretColor:(MTColor *)caretColor
+{
+ _caretView.caretColor = caretColor;
+}
+
+- (MTColor *)caretColor
+{
+ return _caretView.caretColor;
+}
+
+- (void)setBackgroundColor:(MTColor *)backgroundColor
{
[super setBackgroundColor:backgroundColor];
self.label.backgroundColor = backgroundColor;
@@ -136,12 +153,12 @@ - (CGFloat)fontSize
return self.label.fontSize;
}
-- (void)setContentInsets:(UIEdgeInsets)contentInsets
+- (void)setContentInsets:(MTEdgeInsets)contentInsets
{
self.label.contentInsets = contentInsets;
}
-- (UIEdgeInsets)contentInsets
+- (MTEdgeInsets)contentInsets
{
return self.label.contentInsets;
}
@@ -153,82 +170,55 @@ - (CGSize) mathDisplaySize
#pragma mark - Custom user interaction
-- (UIView *)inputView
-{
- return self.keyboard;
-}
-/**
- UIResponder protocol override.
- Our view can become first responder to receive user text input.
- */
-- (BOOL)canBecomeFirstResponder
+- (void)doBecomeFirstResponder
{
- return YES;
-}
+ if (_insertionIndex == nil) {
+ _insertionIndex = [MTMathListIndex level0Index:self.mathList.atoms.count];
+ }
-- (BOOL)becomeFirstResponder
-{
- BOOL canBecome = [super becomeFirstResponder];
- if (canBecome) {
- if (_insertionIndex == nil) {
- _insertionIndex = [MTMathListIndex level0Index:self.mathList.atoms.count];
- }
+ [self.keyboard startedEditing:self];
- [self.keyboard startedEditing:self];
-
- [self insertionPointChanged];
- if ([self.delegate respondsToSelector:@selector(didBeginEditing:)]) {
- [self.delegate didBeginEditing:self];
- }
- } else {
- // Sometimes it takes some time
- // [self performSelector:@selector(startEditing) withObject:nil afterDelay:0.0];
+ [self insertionPointChanged];
+ if ([self.delegate respondsToSelector:@selector(didBeginEditing:)]) {
+ [self.delegate didBeginEditing:self];
}
- return canBecome;
}
/**
UIResponder protocol override.
Called when our view is being asked to resign first responder state.
*/
-- (BOOL)resignFirstResponder
+- (void)doResignFirstResponder
{
- BOOL val = YES;
- if ([self isFirstResponder]) {
- [self.keyboard finishedEditing:self];
- val = [super resignFirstResponder];
- [self insertionPointChanged];
- if ([self.delegate respondsToSelector:@selector(didEndEditing:)]) {
- [self.delegate didEndEditing:self];
- }
+ [self.keyboard finishedEditing:self];
+ [self insertionPointChanged];
+ if ([self.delegate respondsToSelector:@selector(didEndEditing:)]) {
+ [self.delegate didEndEditing:self];
}
- return val;
-}
-
-/**
- UIGestureRecognizerDelegate method.
- Called to determine if we want to handle a given gesture.
- */
-- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gesture shouldReceiveTouch:(UITouch *)touch
-{
- // If gesture touch occurs in our view, we want to handle it
- return YES;
- //return (touch.view == self);
}
- (void) startEditing
{
if (![self isFirstResponder]) {
// Become first responder state (which shows software keyboard, if applicable).
+ #if TARGET_OS_OSX
+ [self.window makeFirstResponder:self];
+ #else
[self becomeFirstResponder];
+ #endif
}
}
/**
Our tap gesture recognizer selector that enters editing mode, or if already in editing mode, updates the text insertion point.
*/
-- (void)tap:(UITapGestureRecognizer *)tap
+- (void)tap:(MTTapGestureRecognizer *)tap
+{
+ [self handleTapAtPoint:[tap locationInView:self]];
+}
+
+- (void)handleTapAtPoint:(CGPoint)tapPoint
{
if (![self isFirstResponder]) {
_insertionIndex = nil;
@@ -236,7 +226,7 @@ - (void)tap:(UITapGestureRecognizer *)tap
[self startEditing];
} else {
// If already editing move the cursor and show handle
- _insertionIndex = [self closestIndexToPoint:[tap locationInView:self]];
+ _insertionIndex = [self closestIndexToPoint:tapPoint];
if (_insertionIndex == nil) {
_insertionIndex = [MTMathListIndex level0Index:self.mathList.atoms.count];
}
@@ -245,21 +235,17 @@ - (void)tap:(UITapGestureRecognizer *)tap
}
}
-- (void)clearTapped:(UITapGestureRecognizer *)tap
-{
- [self clear];
-}
-
- (void)clear
{
self.mathList = [MTMathList new];
[self insertionPointChanged];
+ [_caretView showHandle:NO];
}
- (void)moveCaretToPoint:(CGPoint)point
{
_insertionIndex = [self closestIndexToPoint:point];
- [_caretView showHandle:NO];
+// [_caretView showHandle:NO];
[self insertionPointChanged];
}
@@ -825,33 +811,6 @@ - (BOOL)hasText
return NO;
}
-#pragma mark - UITextInputTraits
-
-- (UITextAutocapitalizationType)autocapitalizationType
-{
- return UITextAutocapitalizationTypeNone;
-}
-
-- (UITextAutocorrectionType)autocorrectionType
-{
- return UITextAutocorrectionTypeNo;
-}
-
-- (UIReturnKeyType)returnKeyType
-{
- return UIReturnKeyDefault;
-}
-
-- (UITextSpellCheckingType)spellCheckingType
-{
- return UITextSpellCheckingTypeNo;
-}
-
-- (UIKeyboardType)keyboardType
-{
- return UIKeyboardTypeASCIICapable;
-}
-
#pragma mark - Hit Testing
@@ -876,16 +835,6 @@ - (CGPoint)caretRectForIndex:(MTMathListIndex *)index
return [self.label.displayList caretPositionForIndex:index];
}
-- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
-{
- BOOL inside = [super pointInside:point withEvent:event];
- if (inside) {
- return YES;
- }
- // check if a point is in the caret view.
- return [_caretView pointInside:[self convertPoint:point toView:_caretView] withEvent:event];
-}
-
#pragma mark - Highlighting
- (void)highlightCharacterAtIndex:(MTMathListIndex *)index
@@ -908,118 +857,4 @@ - (void) clearHighlights
[self.label setNeedsLayout];
}
-#pragma mark - UITextInput
-
-// These are blank just to get a UITextInput implementation, to fix the dictation button bug.
-// Proposed fix from: http://stackoverflow.com/questions/20980898/work-around-for-dictation-custom-text-view-bug
-
-@synthesize beginningOfDocument;
-@synthesize endOfDocument;
-@synthesize inputDelegate;
-@synthesize markedTextRange;
-@synthesize markedTextStyle;
-@synthesize selectedTextRange;
-@synthesize tokenizer;
-
-- (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction
-{
- return UITextWritingDirectionLeftToRight;
-}
-
-- (CGRect)caretRectForPosition:(UITextPosition *)position
-{
- return CGRectZero;
-}
-
-- (void)unmarkText
-{
-
-}
-
-- (UITextRange *)characterRangeAtPoint:(CGPoint)point
-{
- return nil;
-}
-- (UITextRange *)characterRangeByExtendingPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction
-{
- return nil;
-}
-- (UITextPosition *)closestPositionToPoint:(CGPoint)point
-{
- return nil;
-}
-- (UITextPosition *)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange *)range
-{
- return nil;
-}
-- (NSComparisonResult)comparePosition:(UITextPosition *)position toPosition:(UITextPosition *)other
-{
- return NSOrderedSame;
-}
-- (void)dictationRecognitionFailed
-{
-}
-- (void)dictationRecordingDidEnd
-{
-}
-- (CGRect)firstRectForRange:(UITextRange *)range
-{
- return CGRectZero;
-}
-
-- (CGRect)frameForDictationResultPlaceholder:(id)placeholder
-{
- return CGRectZero;
-}
-- (void)insertDictationResult:(NSArray *)dictationResult
-{
-}
-- (id)insertDictationResultPlaceholder
-{
- return nil;
-}
-
-- (NSInteger)offsetFromPosition:(UITextPosition *)fromPosition toPosition:(UITextPosition *)toPosition
-{
- return 0;
-}
-- (UITextPosition *)positionFromPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction offset:(NSInteger)offset
-{
- return nil;
-}
-- (UITextPosition *)positionFromPosition:(UITextPosition *)position offset:(NSInteger)offset
-{
- return nil;
-}
-
-- (UITextPosition *)positionWithinRange:(UITextRange *)range farthestInDirection:(UITextLayoutDirection)direction
-{
- return nil;
-}
-- (void)removeDictationResultPlaceholder:(id)placeholder willInsertResult:(BOOL)willInsertResult
-{
-}
-- (void)replaceRange:(UITextRange *)range withText:(NSString *)text
-{
-}
-- (NSArray *)selectionRectsForRange:(UITextRange *)range
-{
- return nil;
-}
-- (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection forRange:(UITextRange *)range
-{
-}
-- (void)setMarkedText:(NSString *)markedText selectedRange:(NSRange)selectedRange
-{
-}
-
-- (NSString *)textInRange:(UITextRange *)range
-{
- return nil;
-}
-- (UITextRange *)textRangeFromPosition:(UITextPosition *)fromPosition toPosition:(UITextPosition *)toPosition
-{
- return nil;
-}
-
@end
diff --git a/mathEditor/editor/MTEditableMathLabel.h b/MathEditorObjC/mathEditor/include/MTEditableMathLabel.h
similarity index 74%
rename from mathEditor/editor/MTEditableMathLabel.h
rename to MathEditorObjC/mathEditor/include/MTEditableMathLabel.h
index 318a7fb..35bb11d 100644
--- a/mathEditor/editor/MTEditableMathLabel.h
+++ b/MathEditorObjC/mathEditor/include/MTEditableMathLabel.h
@@ -8,11 +8,14 @@
// MIT license. See the LICENSE file for details.
//
-#import
-#import
+@import iosMath;
+#import "MTKeyInput.h"
+#include "MTMathList.h"
@class MTEditableMathLabel;
@class MTMathListIndex;
+@class MTCancelView;
+@class MTCaretView;
/** Delegate for the `MTEditableMathLabel`. All methods are optional. */
@protocol MTEditableMathLabelDelegate
@@ -50,28 +53,31 @@
this protocol.
This protocol informs the keyboard when a particular `MTEditableMathUILabel` is being edited.
- The keyboard should use this information to send `UIKeyInput` messages to the label.
+ The keyboard should use this information to send `MTKeyInput` messages to the label.
This protocol inherits from `MTMathKeyboardTraits`.
*/
@protocol MTMathKeyboard
-- (void) startedEditing:(UIView*) label;
-- (void) finishedEditing:(UIView*) label;
+- (void) startedEditing:(MTView*) label;
+- (void) finishedEditing:(MTView*) label;
@end
-@interface MTEditableMathLabel : UIView
+@interface MTEditableMathLabel : MTView
@property (nonatomic) MTMathList* mathList;
-@property (nonatomic) UIColor* highlightColor;
+@property (nonatomic) MTColor* highlightColor;
+@property (nonatomic) MTColor* textColor;
+@property (nonatomic) MTColor* caretColor;
-@property (nonatomic) UIImageView* cancelImage;
+@property (nonatomic) MTCancelView* cancelImage;
+@property (nonatomic) MTCaretView* caretView;
@property (nonatomic, weak) id delegate;
-@property (nonatomic, weak) UIView* keyboard;
+@property (nonatomic, weak) MTView* keyboard;
@property (nonatomic) CGFloat fontSize;
-@property (nonatomic) IBInspectable UIEdgeInsets contentInsets;
+@property (nonatomic) MTEdgeInsets contentInsets;
- (void) clear;
@@ -86,4 +92,8 @@
- (CGSize) mathDisplaySize;
+// Compatibility?
+- (void)doLayout;
+- (void)doBecomeFirstResponder;
+- (void)doResignFirstResponder;
@end
diff --git a/MathEditorObjC/mathEditor/include/MTKeyInput.h b/MathEditorObjC/mathEditor/include/MTKeyInput.h
new file mode 100644
index 0000000..9eab026
--- /dev/null
+++ b/MathEditorObjC/mathEditor/include/MTKeyInput.h
@@ -0,0 +1,24 @@
+//
+// MTKeyInput.h
+//
+// Created for cross-platform key input abstraction.
+//
+
+#import "MTConfig.h"
+
+#if TARGET_OS_IPHONE
+
+#import
+#define MTKeyInput UIKeyInput
+
+#else
+
+@protocol MTKeyInput
+
+- (void)insertText:(NSString *)text;
+- (void)deleteBackward;
+- (BOOL)hasText;
+
+@end
+
+#endif
diff --git a/MathEditorObjC/mathEditor/internal/MTCancelView.h b/MathEditorObjC/mathEditor/internal/MTCancelView.h
new file mode 100644
index 0000000..8d243c6
--- /dev/null
+++ b/MathEditorObjC/mathEditor/internal/MTCancelView.h
@@ -0,0 +1,13 @@
+//
+// MTCancelView.h
+//
+// Created for the editable label clear affordance.
+//
+
+@import iosMath;
+
+@interface MTCancelView : MTView
+
+- (instancetype)initWithTarget:(id)target action:(SEL)action;
+
+@end
diff --git a/MathEditorObjC/mathEditor/internal/MTCancelView.m b/MathEditorObjC/mathEditor/internal/MTCancelView.m
new file mode 100644
index 0000000..9d40ec9
--- /dev/null
+++ b/MathEditorObjC/mathEditor/internal/MTCancelView.m
@@ -0,0 +1,50 @@
+//
+// MTCancelView.m
+//
+// Created for the editable label clear affordance.
+//
+
+#import "MTCancelView.h"
+#import "MTTapGestureRecognizer.h"
+#import "MTView/MTView+AutoLayout.h"
+
+@interface MTCancelView ()
+
+#if TARGET_OS_IPHONE
+@property (nonatomic, strong) UIImageView *imageView;
+#else
+@property (nonatomic, strong) NSImageView *imageView;
+#endif
+
+@end
+
+@implementation MTCancelView
+
+- (instancetype)initWithTarget:(id)target action:(SEL)action
+{
+ self = [super initWithFrame:CGRectZero];
+ if (self) {
+#if TARGET_OS_IPHONE
+ UIImage *image = [UIImage systemImageNamed:@"xmark.circle"];
+ image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
+ _imageView = [[UIImageView alloc] initWithImage:image];
+ _imageView.contentMode = UIViewContentModeScaleAspectFit;
+ _imageView.tintColor = [MTColor secondaryLabelColor];
+#else
+ NSImage *image = [NSImage imageWithSystemSymbolName:@"xmark.circle" accessibilityDescription:nil];
+ _imageView = [[NSImageView alloc] initWithFrame:CGRectZero];
+ _imageView.image = image;
+ _imageView.imageScaling = NSImageScaleProportionallyUpOrDown;
+ _imageView.contentTintColor = [MTColor secondaryLabelColor];
+#endif
+ [self addSubview:_imageView];
+ [_imageView pinToSuperview];
+
+ MTTapGestureRecognizer *tapRecognizer = [[MTTapGestureRecognizer alloc] initWithTarget:target action:action];
+ [self addGestureRecognizer:tapRecognizer];
+ self.hidden = YES;
+ }
+ return self;
+}
+
+@end
diff --git a/mathEditor/internal/MTCaretView.h b/MathEditorObjC/mathEditor/internal/MTCaretView.h
similarity index 82%
rename from mathEditor/internal/MTCaretView.h
rename to MathEditorObjC/mathEditor/internal/MTCaretView.h
index 1c6bab8..09beae2 100644
--- a/mathEditor/internal/MTCaretView.h
+++ b/MathEditorObjC/mathEditor/internal/MTCaretView.h
@@ -8,13 +8,13 @@
// MIT license. See the LICENSE file for details.
//
-#import
+#import "MTConfig.h"
@class MTEditableMathLabel;
-@interface MTCaretView : UIView
+@interface MTCaretView : MTView
-@property (nonatomic) UIColor* caretColor;
+@property (nonatomic) MTColor* caretColor;
- (id) initWithEditor:(MTEditableMathLabel*)label;
diff --git a/mathEditor/internal/MTCaretView.m b/MathEditorObjC/mathEditor/internal/MTCaretView.m
similarity index 63%
rename from mathEditor/internal/MTCaretView.m
rename to MathEditorObjC/mathEditor/internal/MTCaretView.m
index 04e9f89..1e7b041 100644
--- a/mathEditor/internal/MTCaretView.m
+++ b/MathEditorObjC/mathEditor/internal/MTCaretView.m
@@ -10,6 +10,10 @@
#import "MTCaretView.h"
#import "MTEditableMathLabel.h"
+#import "MTConfig.h"
+#import "MTView/MTView+Layout.h"
+#import "MTView/MTView+HitTest.h"
+#import "NSBezierPath+addLineToPoint.h"
static const NSTimeInterval InitialBlinkDelay = 0.7;
static const NSTimeInterval BlinkRate = 0.5;
@@ -27,15 +31,15 @@ static NSInteger getCaretHeight() {
return kCaretAscent + kCaretDescent;
}
-@interface MTCaretHandle : UIView
+@interface MTCaretHandle : MTView
@property (nonatomic, weak) MTEditableMathLabel* label;
@end
@implementation MTCaretHandle {
- UIBezierPath* _path;
- UIColor* _color;
+ MTBezierPath* _path;
+ MTColor* _color;
}
- (id) initWithFrame:(CGRect)frame
@@ -47,9 +51,9 @@ - (id) initWithFrame:(CGRect)frame
return self;
}
-- (UIBezierPath*) createHandlePath
+- (MTBezierPath*) createHandlePath
{
- UIBezierPath* path = [UIBezierPath bezierPath];
+ MTBezierPath* path = [MTBezierPath bezierPath];
CGSize size = self.bounds.size;
[path moveToPoint:CGPointMake(size.width/2, 0)];
[path addLineToPoint:CGPointMake(size.width, size.height/4)];
@@ -60,57 +64,137 @@ - (UIBezierPath*) createHandlePath
return path;
}
-- (void) layoutSubviews
-{
- _path = [self createHandlePath];
-}
-
- (void)drawRect:(CGRect)rect
{
[_color setFill];
[_path fill];
}
-- (void) setColor:(UIColor*) color
+- (void) setColor:(MTColor*) color
{
- _color = [color colorWithAlphaComponent:0.6];
+ _color = [color colorWithAlphaComponent:0.7];
+}
+
+- (void)interactionBegan
+{
+ _color = [_color colorWithAlphaComponent:1.0];
+ [self setNeedsDisplay];
+}
+
+- (void)interactionEnded
+{
+ _color = [_color colorWithAlphaComponent:0.6];
+ [self setNeedsDisplay];
+}
+
+- (void)handleDragAtLocalPoint:(CGPoint)loc
+{
+ CGPoint caretPoint = CGPointMake(loc.x, loc.y - self.frame.origin.y);
+ CGPoint labelPoint = [_label convertPoint:caretPoint fromView:self];
+ [_label moveCaretToPoint:labelPoint]; // puts the point at the top to the top of the current caret
+}
+
+- (CGRect)hitArea
+{
+ // Create a hit area around the center.
+ CGSize size = self.bounds.size;
+ return CGRectMake((size.width - kCaretHandleHitAreaSize) / 2,
+ (size.height - kCaretHandleHitAreaSize) / 2,
+ kCaretHandleHitAreaSize,
+ kCaretHandleHitAreaSize);
+}
+
+#if TARGET_OS_IPHONE
+
+- (void) layoutSubviews
+{
+ [super layoutSubviews];
+ _path = [self createHandlePath];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
+ [self interactionBegan];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
-
+ [self interactionEnded];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// From apple documentation
UITouch *aTouch = [touches anyObject];
- CGPoint loc = [aTouch locationInView:self];
- CGRect frame = self.frame;
- CGPoint caretPoint = CGPointMake(loc.x, loc.y - frame.origin.y); // puts the point at the top to the top of the current caret
- CGPoint labelPoint = [_label convertPoint:caretPoint fromView:self];
- [_label moveCaretToPoint:labelPoint];
+ [self handleDragAtLocalPoint:[aTouch locationInView:self]];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
-
+ [self interactionEnded];
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
- // Create a hit area around the center.
- CGSize size = self.bounds.size;
- CGRect hitArea = CGRectMake((size.width - kCaretHandleHitAreaSize)/2, (size.height - kCaretHandleHitAreaSize)/2, kCaretHandleHitAreaSize, kCaretHandleHitAreaSize);
- return CGRectContainsPoint(hitArea, point);
+ return CGRectContainsPoint([self hitArea], point);
+}
+
+#endif
+
+
+#if TARGET_OS_OSX
+- (void) layout
+{
+ [super layout];
+ _path = [self createHandlePath];
+}
+
+- (BOOL) isFlipped {
+ return YES;
}
+- (BOOL)acceptsFirstMouse:(NSEvent *)event
+{
+ return YES;
+}
+
+- (void)mouseDown:(NSEvent *)event
+{
+ [self interactionBegan];
+}
+
+- (void)mouseDragged:(NSEvent *)event
+{
+ NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
+ [self handleDragAtLocalPoint:point];
+}
+
+- (void)mouseUp:(NSEvent *)event
+{
+ [self interactionEnded];
+}
+
+- (void)mouseCancelled:(NSEvent *)event {
+ [self interactionEnded];
+}
+
+- (NSView *)hitTest:(NSPoint)point
+{
+ if (self.hidden) {
+ return nil;
+ }
+ CGPoint localPoint = [self convertPoint:point fromView:self.superview];
+ if (CGRectContainsPoint([self hitArea], localPoint)) {
+ return self;
+ }
+ return nil;
+}
+
+#endif // TARGET_OS_OSX
+
@end
+
@interface MTCaretView ()
@property (nonatomic) NSTimer *blinkTimer;
@@ -119,7 +203,7 @@ @interface MTCaretView ()
@implementation MTCaretView {
- UIView *_blinker;
+ MTView *_blinker;
MTCaretHandle *_handle;
CGFloat _scale;
}
@@ -129,11 +213,11 @@ - (id)initWithEditor:(MTEditableMathLabel*)label
self = [super initWithFrame:CGRectZero];
if (self) {
_scale = label.fontSize / kCaretFontSize;
- _blinker = [[UIView alloc] initWithFrame:CGRectZero];
+ _blinker = [[MTView alloc] initWithFrame:CGRectZero];
_blinker.backgroundColor = self.caretColor;
[self addSubview:_blinker];
_handle = [[MTCaretHandle alloc] initWithFrame:CGRectMake(0, 0, kCaretHandleWidth * _scale, kCaretHandleHeight *_scale)];
- _handle.backgroundColor = [UIColor clearColor];
+ _handle.backgroundColor = [MTColor clearColor];
_handle.hidden = YES;
_handle.label = label;
[self addSubview:_handle];
@@ -154,7 +238,7 @@ - (void) setFontSize:(CGFloat)fontSize
[self setNeedsLayout];
}
-- (void) layoutSubviews
+- (void) doLayout
{
_blinker.frame = CGRectMake(0, 0, kCaretWidth * _scale, getCaretHeight() *_scale);
_handle.frame = CGRectMake(-(kCaretHandleWidth - kCaretWidth) * _scale/2, (getCaretHeight() + kCaretHandleDescent) *_scale, kCaretHandleWidth *_scale, kCaretHandleHeight *_scale);
@@ -171,7 +255,6 @@ - (void)blink
_blinker.hidden = !_blinker.hidden;
}
-
// UIView didMoveToSuperview override to set up blink timers after caret view created in superview.
- (void)didMoveToSuperview
{
@@ -187,7 +270,6 @@ - (void)didMoveToSuperview
}
}
-
// Helper method to set an initial blink delay
- (void)delayBlink
{
@@ -202,13 +284,14 @@ - (void)dealloc
[_blinkTimer invalidate];
}
-- (void)setCaretColor:(UIColor *)caretColor
+- (void)setCaretColor:(MTColor *)caretColor
{
_caretColor = caretColor;
_handle.color = caretColor;
_blinker.backgroundColor = self.caretColor;
}
+#if TARGET_OS_IPHONE
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
if (!_handle.hidden) {
return [_handle pointInside:[self convertPoint:point toView:_handle] withEvent:event];
@@ -217,6 +300,32 @@ - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
}
}
+- (void) layoutSubviews
+{
+ [super layoutSubviews];
+ [self doLayout];
+}
+#endif // TARGET_OS_IPHONE
-@end
+#if TARGET_OS_OSX
+- (void)viewDidMoveToSuperview
+{
+ [super viewDidMoveToSuperview];
+ [self didMoveToSuperview];
+}
+- (void) layout {
+ [super layout];
+ [self doLayout];
+}
+
+- (BOOL)isFlipped {
+ return YES;
+}
+
+- (NSView *)hitTest:(NSPoint)point {
+ return [self hitTestOutsideBounds:point];
+}
+#endif // TARGET_OS_OSX
+
+@end
diff --git a/mathEditor/internal/MTDisplay+Editing.h b/MathEditorObjC/mathEditor/internal/MTDisplay+Editing.h
similarity index 92%
rename from mathEditor/internal/MTDisplay+Editing.h
rename to MathEditorObjC/mathEditor/internal/MTDisplay+Editing.h
index 437232a..7af2631 100644
--- a/mathEditor/internal/MTDisplay+Editing.h
+++ b/MathEditorObjC/mathEditor/internal/MTDisplay+Editing.h
@@ -20,10 +20,10 @@
- (CGPoint) caretPositionForIndex:(MTMathListIndex*) index;
// Highlight the character(s) at the given index.
-- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(UIColor*) color;
+- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(MTColor*) color;
// Highlight the entire display with the given color
-- (void) highlightWithColor:(UIColor*) color;
+- (void) highlightWithColor:(MTColor*) color;
@end
diff --git a/mathEditor/internal/MTDisplay+Editing.m b/MathEditorObjC/mathEditor/internal/MTDisplay+Editing.m
similarity index 97%
rename from mathEditor/internal/MTDisplay+Editing.m
rename to MathEditorObjC/mathEditor/internal/MTDisplay+Editing.m
index bc719e4..a60ef5b 100644
--- a/mathEditor/internal/MTDisplay+Editing.m
+++ b/MathEditorObjC/mathEditor/internal/MTDisplay+Editing.m
@@ -93,11 +93,11 @@ - (CGPoint)caretPositionForIndex:(MTMathListIndex *)index
return kInvalidPosition;
}
-- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(UIColor*) color
+- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(MTColor*) color
{
}
-- (void)highlightWithColor:(UIColor *)color
+- (void)highlightWithColor:(MTColor *)color
{
}
@end
@@ -113,9 +113,9 @@ - (MTMathListIndex*) closestIndexToPoint:(CGPoint) point;
- (CGPoint) caretPositionForIndex:(MTMathListIndex*) index;
// Highlight the character(s) at the given index.
-- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(UIColor*) color;
+- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(MTColor*) color;
-- (void)highlightWithColor:(UIColor *)color;
+- (void)highlightWithColor:(MTColor *)color;
@end
@@ -156,7 +156,7 @@ - (CGPoint)caretPositionForIndex:(MTMathListIndex *)index
}
-- (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(UIColor *)color
+- (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(MTColor *)color
{
assert(NSLocationInRange(index.atomIndex, self.range));
assert(index.subIndexType == kMTSubIndexTypeNone || index.subIndexType == kMTSubIndexTypeNucleus);
@@ -173,7 +173,7 @@ - (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(UIColor *)colo
self.attributedString = attrStr;
}
-- (void)highlightWithColor:(UIColor *)color
+- (void)highlightWithColor:(MTColor *)color
{
NSMutableAttributedString* attrStr = self.attributedString.mutableCopy;
[attrStr addAttribute:(NSString*)kCTForegroundColorAttributeName value:(id)[color CGColor]
@@ -222,9 +222,9 @@ - (MTMathListIndex*) closestIndexToPoint:(CGPoint) point;
- (CGPoint) caretPositionForIndex:(MTMathListIndex*) index;
// Highlight the character(s) at the given index.
-- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(UIColor*) color;
+- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(MTColor*) color;
-- (void)highlightWithColor:(UIColor *)color;
+- (void)highlightWithColor:(MTColor *)color;
- (MTMathListDisplay*) subAtomForIndexType:(MTMathListSubIndexType) type;
@@ -261,13 +261,13 @@ - (CGPoint)caretPositionForIndex:(MTMathListIndex *)index
return CGPointMake(self.position.x, self.position.y);
}
-- (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(UIColor *)color
+- (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(MTColor *)color
{
assert(index.subIndexType == kMTSubIndexTypeNone);
[self highlightWithColor:color];
}
-- (void)highlightWithColor:(UIColor *)color
+- (void)highlightWithColor:(MTColor *)color
{
[self.numerator highlightWithColor:color];
[self.denominator highlightWithColor:color];
@@ -306,9 +306,9 @@ - (MTMathListIndex*) closestIndexToPoint:(CGPoint) point;
- (CGPoint) caretPositionForIndex:(MTMathListIndex*) index;
// Highlight the character(s) at the given index.
-- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(UIColor*) color;
+- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(MTColor*) color;
-- (void)highlightWithColor:(UIColor *)color;
+- (void)highlightWithColor:(MTColor *)color;
- (MTMathListDisplay*) subAtomForIndexType:(MTMathListSubIndexType) type;
@@ -348,13 +348,13 @@ - (CGPoint)caretPositionForIndex:(MTMathListIndex *)index
return CGPointMake(self.position.x, self.position.y);
}
-- (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(UIColor *)color
+- (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(MTColor *)color
{
assert(index.subIndexType == kMTSubIndexTypeNone);
[self highlightWithColor:color];
}
-- (void)highlightWithColor:(UIColor *)color
+- (void)highlightWithColor:(MTColor *)color
{
[self.radicand highlightWithColor:color];
}
@@ -393,9 +393,9 @@ - (MTMathListIndex*) closestIndexToPoint:(CGPoint) point;
- (CGPoint) caretPositionForIndex:(MTMathListIndex*) index;
// Highlight the character(s) at the given index.
-- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(UIColor*) color;
+- (void) highlightCharacterAtIndex:(MTMathListIndex*) index color:(MTColor*) color;
-- (void)highlightWithColor:(UIColor *)color;
+- (void)highlightWithColor:(MTColor *)color;
@end
@@ -570,7 +570,7 @@ - (CGPoint)caretPositionForIndex:(MTMathListIndex *)index
}
-- (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(UIColor *)color
+- (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(MTColor *)color
{
if (!index) {
return;
@@ -586,7 +586,7 @@ - (void)highlightCharacterAtIndex:(MTMathListIndex *)index color:(UIColor *)colo
}
}
-- (void)highlightWithColor:(UIColor *)color
+- (void)highlightWithColor:(MTColor *)color
{
for (MTDisplay* atom in self.subDisplays) {
[atom highlightWithColor:color];
diff --git a/mathEditor/internal/MTMathList+Editing.h b/MathEditorObjC/mathEditor/internal/MTMathList+Editing.h
similarity index 100%
rename from mathEditor/internal/MTMathList+Editing.h
rename to MathEditorObjC/mathEditor/internal/MTMathList+Editing.h
diff --git a/mathEditor/internal/MTMathList+Editing.m b/MathEditorObjC/mathEditor/internal/MTMathList+Editing.m
similarity index 100%
rename from mathEditor/internal/MTMathList+Editing.m
rename to MathEditorObjC/mathEditor/internal/MTMathList+Editing.m
diff --git a/MathEditorObjC/mathEditor/internal/MTTapGestureRecognizer.h b/MathEditorObjC/mathEditor/internal/MTTapGestureRecognizer.h
new file mode 100644
index 0000000..ff47ffb
--- /dev/null
+++ b/MathEditorObjC/mathEditor/internal/MTTapGestureRecognizer.h
@@ -0,0 +1,19 @@
+//
+// MTTapGestureRecognizer.h
+//
+// Small cross-platform tap gesture abstraction.
+//
+
+@import iosMath;
+
+#if TARGET_OS_IPHONE
+
+#import
+#define MTTapGestureRecognizer UITapGestureRecognizer
+
+#else
+
+#import
+#define MTTapGestureRecognizer NSClickGestureRecognizer
+
+#endif
diff --git a/MathEditorObjC/mathEditor/internal/MTView/MTView+AutoLayout.h b/MathEditorObjC/mathEditor/internal/MTView/MTView+AutoLayout.h
new file mode 100644
index 0000000..62cf81b
--- /dev/null
+++ b/MathEditorObjC/mathEditor/internal/MTView/MTView+AutoLayout.h
@@ -0,0 +1,20 @@
+//
+// MTView+AutoLayout.h
+// MathEditor
+//
+// Created by Madiyar Aitbayev on 20/03/2026.
+//
+
+#import "MXView.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface MXView (AutoLayout)
+
+- (void)pinToSuperview;
+
+- (void)pinToSuperviewWithTop:(CGFloat)top leading:(CGFloat)leading bottom:(CGFloat)bottom trailing:(CGFloat)trailing;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/MathEditorObjC/mathEditor/internal/MTView/MTView+AutoLayout.m b/MathEditorObjC/mathEditor/internal/MTView/MTView+AutoLayout.m
new file mode 100644
index 0000000..be85c28
--- /dev/null
+++ b/MathEditorObjC/mathEditor/internal/MTView/MTView+AutoLayout.m
@@ -0,0 +1,32 @@
+//
+// MTView+AutoLayout.m
+// MathEditor
+//
+// Created by Madiyar Aitbayev on 20/03/2026.
+//
+
+#import "MTConfig.h"
+#import "MTView+AutoLayout.h"
+
+@implementation MXView (AutoLayout)
+
+- (void)pinToSuperview
+{
+ [self pinToSuperviewWithTop:0 leading:0 bottom:0 trailing:0];
+}
+
+- (void)pinToSuperviewWithTop:(CGFloat)top leading:(CGFloat)leading bottom:(CGFloat)bottom trailing:(CGFloat)trailing
+{
+ MTView *superview = self.superview;
+ if (!superview)
+ return;
+ self.translatesAutoresizingMaskIntoConstraints = NO;
+ [NSLayoutConstraint activateConstraints:@[
+ [self.topAnchor constraintEqualToAnchor:superview.topAnchor constant:top],
+ [self.leadingAnchor constraintEqualToAnchor:superview.leadingAnchor constant:leading],
+ [self.trailingAnchor constraintEqualToAnchor:superview.trailingAnchor constant:-trailing],
+ [self.bottomAnchor constraintEqualToAnchor:superview.bottomAnchor constant:-bottom]
+ ]];
+}
+
+@end
diff --git a/MathEditorObjC/mathEditor/internal/MTView/MTView+FirstResponder.h b/MathEditorObjC/mathEditor/internal/MTView/MTView+FirstResponder.h
new file mode 100644
index 0000000..67a2b94
--- /dev/null
+++ b/MathEditorObjC/mathEditor/internal/MTView/MTView+FirstResponder.h
@@ -0,0 +1,22 @@
+//
+// MTView+FirstResponder.h
+// MathEditor
+//
+// Created by Madiyar Aitbayev on 20/03/2026.
+//
+
+#import "MTConfig.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+#if TARGET_OS_OSX
+
+@interface NSView (FirstResponder)
+
+@property (nonatomic, readonly) BOOL isFirstResponder;
+
+@end
+
+#endif // TARGET_OS_OSX
+
+NS_ASSUME_NONNULL_END
diff --git a/MathEditorObjC/mathEditor/internal/MTView/MTView+FirstResponder.m b/MathEditorObjC/mathEditor/internal/MTView/MTView+FirstResponder.m
new file mode 100644
index 0000000..df4cda9
--- /dev/null
+++ b/MathEditorObjC/mathEditor/internal/MTView/MTView+FirstResponder.m
@@ -0,0 +1,26 @@
+//
+// MTView+FirstResponder.m
+// MathEditor
+//
+// Created by Madiyar Aitbayev on 20/03/2026.
+//
+
+#import "MTConfig.h"
+#import "MTView+FirstResponder.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+#if TARGET_OS_OSX
+
+@implementation NSView (FirstResponder)
+
+- (BOOL)isFirstResponder
+{
+ return self.window.firstResponder == self;
+}
+
+@end
+
+#endif
+
+NS_ASSUME_NONNULL_END
diff --git a/MathEditorObjC/mathEditor/internal/MTView/MTView+HitTest.h b/MathEditorObjC/mathEditor/internal/MTView/MTView+HitTest.h
new file mode 100644
index 0000000..0c87d63
--- /dev/null
+++ b/MathEditorObjC/mathEditor/internal/MTView/MTView+HitTest.h
@@ -0,0 +1,23 @@
+//
+// MTView+HitTest.h
+// MathEditor
+//
+// Created by Madiyar Aitbayev on 21/03/2026.
+//
+
+#if TARGET_OS_OSX
+
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface NSView (HitTest)
+
+- (NSView *)hitTestOutsideBounds:(NSPoint)point;
+- (NSView *)hitTestOutsideBounds:(NSPoint)point ignoringSubviews:(NSArray *)ignoredSubviews;
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif // TARGET_OS_OSX
diff --git a/MathEditorObjC/mathEditor/internal/MTView/MTView+HitTest.m b/MathEditorObjC/mathEditor/internal/MTView/MTView+HitTest.m
new file mode 100644
index 0000000..59822b5
--- /dev/null
+++ b/MathEditorObjC/mathEditor/internal/MTView/MTView+HitTest.m
@@ -0,0 +1,46 @@
+//
+// MTView+HitTest.m
+// MathEditor
+//
+// Created by Madiyar Aitbayev on 21/03/2026.
+//
+
+#import "MTView+HitTest.h"
+
+#if TARGET_OS_OSX
+
+NS_ASSUME_NONNULL_BEGIN
+
+@implementation NSView (HitTest)
+
+- (NSView *)hitTestOutsideBounds:(NSPoint)point
+{
+ return [self hitTestOutsideBounds:point ignoringSubviews:@[]];
+}
+
+- (NSView *)hitTestOutsideBounds:(NSPoint)point ignoringSubviews:(NSArray *)ignoredSubviews
+{
+ if (self.hidden) {
+ return nil;
+ }
+ NSPoint localPoint = [self convertPoint:point fromView:self.superview];
+ for (NSView *child in [self.subviews reverseObjectEnumerator]) {
+ if ([ignoredSubviews containsObject:child]) {
+ continue;
+ }
+ NSView *hitView = [child hitTest:localPoint];
+ if (hitView) {
+ return hitView;
+ }
+ }
+ if (NSPointInRect(localPoint, self.bounds)) {
+ return self;
+ }
+ return nil;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif // TARGET_OS_OSX
diff --git a/MathEditorObjC/mathEditor/internal/MTView/MTView+Layout.h b/MathEditorObjC/mathEditor/internal/MTView/MTView+Layout.h
new file mode 100644
index 0000000..fdeb22b
--- /dev/null
+++ b/MathEditorObjC/mathEditor/internal/MTView/MTView+Layout.h
@@ -0,0 +1,28 @@
+//
+// MTView+Layout.h
+// MathEditor
+//
+// Created by Madiyar Aitbayev on 20/03/2026.
+//
+
+#import "MXView.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface MXView (Layout)
+
+#if TARGET_OS_OSX
+
+- (void)setNeedsLayout;
+
+- (void)setNeedsDisplay;
+
+- (void)layoutIfNeeded;
+
+- (void)bringSubviewToFront:(NSView *)view;
+
+#endif
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/MathEditorObjC/mathEditor/internal/MTView/MTView+Layout.m b/MathEditorObjC/mathEditor/internal/MTView/MTView+Layout.m
new file mode 100644
index 0000000..0d43c50
--- /dev/null
+++ b/MathEditorObjC/mathEditor/internal/MTView/MTView+Layout.m
@@ -0,0 +1,40 @@
+//
+// MTView+Layout.m
+// MathEditor
+//
+// Created by Madiyar Aitbayev on 20/03/2026.
+//
+
+#import "MTView+Layout.h"
+
+@implementation MXView (Layout)
+
+#if TARGET_OS_OSX
+
+- (void)setNeedsLayout
+{
+ [self setNeedsLayout:YES];
+}
+
+- (void)setNeedsDisplay
+{
+ [self setNeedsDisplay:YES];
+}
+
+- (void)layoutIfNeeded
+{
+ [self layoutSubtreeIfNeeded];
+}
+
+- (void)bringSubviewToFront:(NSView *)view
+{
+ if (view.superview != self) {
+ return;
+ }
+ [view removeFromSuperview];
+ [self addSubview:view];
+}
+
+#endif
+
+@end
diff --git a/MathEditorObjC/mathEditor/internal/MTView/MXView.h b/MathEditorObjC/mathEditor/internal/MTView/MXView.h
new file mode 100644
index 0000000..d781e72
--- /dev/null
+++ b/MathEditorObjC/mathEditor/internal/MTView/MXView.h
@@ -0,0 +1,14 @@
+//
+// MXView.h
+// MathEditor
+//
+// Created by Madiyar Aitbayev on 20/03/2026.
+//
+
+#if TARGET_OS_OSX
+#import
+#define MXView NSView
+#else
+#import
+#define MXView UIView
+#endif
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/Contents.json
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 1x.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 1x.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 1x.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 1x.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 2x.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 2x.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 2x.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 2x.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 3x.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 3x.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 3x.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 3x.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/Contents.json
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 1x.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 1x.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 1x.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 1x.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 2x.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 2x.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 2x.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 2x.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 3x.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 3x.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 3x.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 3x.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/blue-button-highlighted.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/blue-button-highlighted.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/blue-button-highlighted.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/blue-button-highlighted.imageset/Contents.json
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/blue-button-highlighted.imageset/blue-pressed.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/blue-button-highlighted.imageset/blue-pressed.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/blue-button-highlighted.imageset/blue-pressed.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/blue-button-highlighted.imageset/blue-pressed.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/Contents.json
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 1x.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 1x.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 1x.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 1x.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 2x.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 2x.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 2x.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 2x.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 3x.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 3x.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 3x.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 3x.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/Contents.json
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 1x.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 1x.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 1x.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 1x.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 2x.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 2x.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 2x.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 2x.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 3x.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 3x.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 3x.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 3x.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/grey-button-disabled.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/grey-button-disabled.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/grey-button-disabled.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/grey-button-disabled.imageset/Contents.json
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/grey-button-disabled.imageset/grey-button-disabled.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/grey-button-disabled.imageset/grey-button-disabled.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/grey-button-disabled.imageset/grey-button-disabled.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/grey-button-disabled.imageset/grey-button-disabled.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/Contents.json
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/keyboard-grey-pressed.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/keyboard-grey-pressed.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/keyboard-grey-pressed.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/keyboard-grey-pressed.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/ipad-background.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/ipad-background.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/ipad-background.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/ipad-background.imageset/Contents.json
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard1x.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard1x.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard1x.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard1x.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard2x.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard2x.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard2x.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard2x.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/iphone-background.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/iphone-background.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/iphone-background.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/iphone-background.imageset/Contents.json
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background1x.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background1x.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background1x.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background1x.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background2x.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background2x.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background2x.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background2x.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/Contents.json
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/kb-dark-blue-pressed.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/kb-dark-blue-pressed.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/kb-dark-blue-pressed.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/kb-dark-blue-pressed.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/Contents.json
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/kb-slide-button-pressed.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/kb-slide-button-pressed.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/kb-slide-button-pressed.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/kb-slide-button-pressed.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/Contents.json
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide11x.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide11x.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide11x.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide11x.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide12x.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide12x.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide12x.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide12x.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/Contents.json
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide21x.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide21x.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide21x.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide21x.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide22x.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide22x.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide22x.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide22x.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/Contents.json
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide31x.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide31x.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide31x.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide31x.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide32x.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide32x.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide32x.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide32x.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/Contents.json
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication1x.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication1x.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication1x.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication1x.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication2x-ipad.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication2x-ipad.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication2x-ipad.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication2x-ipad.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/Contents.json
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication1x-iphone.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication1x-iphone.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication1x-iphone.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication1x-iphone.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication2x-iphone.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication2x-iphone.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication2x-iphone.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication2x-iphone.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/num-button-disabled.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/num-button-disabled.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/num-button-disabled.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/num-button-disabled.imageset/Contents.json
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/num-button-disabled.imageset/grey-button-disabled.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/num-button-disabled.imageset/grey-button-disabled.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/num-button-disabled.imageset/grey-button-disabled.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/num-button-disabled.imageset/grey-button-disabled.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/num-button-highlighted.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/num-button-highlighted.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/num-button-highlighted.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/num-button-highlighted.imageset/Contents.json
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/num-button-highlighted.imageset/keyboard-num-pressed.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/num-button-highlighted.imageset/keyboard-num-pressed.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/num-button-highlighted.imageset/keyboard-num-pressed.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/num-button-highlighted.imageset/keyboard-num-pressed.png
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/Contents.json
diff --git a/MathKeyboardResources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/keyboard-orange-pressed.png b/MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/keyboard-orange-pressed.png
similarity index 100%
rename from MathKeyboardResources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/keyboard-orange-pressed.png
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/keyboard-orange-pressed.png
diff --git a/MathKeyboardResources/MTKeyboard.xib b/MathEditorObjC/mathKeyboard/MathKeyboardResources/MTKeyboard.xib
similarity index 100%
rename from MathKeyboardResources/MTKeyboard.xib
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/MTKeyboard.xib
diff --git a/MathKeyboardResources/MTKeyboardTab2.xib b/MathEditorObjC/mathKeyboard/MathKeyboardResources/MTKeyboardTab2.xib
similarity index 100%
rename from MathKeyboardResources/MTKeyboardTab2.xib
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/MTKeyboardTab2.xib
diff --git a/MathKeyboardResources/MTKeyboardTab3.xib b/MathEditorObjC/mathKeyboard/MathKeyboardResources/MTKeyboardTab3.xib
similarity index 100%
rename from MathKeyboardResources/MTKeyboardTab3.xib
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/MTKeyboardTab3.xib
diff --git a/MathKeyboardResources/MTKeyboardTab4.xib b/MathEditorObjC/mathKeyboard/MathKeyboardResources/MTKeyboardTab4.xib
similarity index 100%
rename from MathKeyboardResources/MTKeyboardTab4.xib
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/MTKeyboardTab4.xib
diff --git a/MathKeyboardResources/MTMathKeyboardRootView.xib b/MathEditorObjC/mathKeyboard/MathKeyboardResources/MTMathKeyboardRootView.xib
similarity index 100%
rename from MathKeyboardResources/MTMathKeyboardRootView.xib
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/MTMathKeyboardRootView.xib
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Backspace Small.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Backspace Small.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Backspace Small.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Backspace Small.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace.imageset/Backspace.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace.imageset/Backspace.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace.imageset/Backspace.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace.imageset/Backspace.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Backspace.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Exponent.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Exponent.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Exponent.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Exponent.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Exponent.imageset/Exponent.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Exponent.imageset/Exponent.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Exponent.imageset/Exponent.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Exponent.imageset/Exponent.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Fraction.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Fraction.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Fraction.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Fraction.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Fraction.imageset/Fraction.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Fraction.imageset/Fraction.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Fraction.imageset/Fraction.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Fraction.imageset/Fraction.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Functions Keyboard.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Functions Keyboard.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Functions Keyboard.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Functions Keyboard.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Functions Symbol.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Functions Symbol.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Functions Symbol.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Functions Symbol.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Keyboard Down.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Keyboard Down.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Keyboard Down.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Keyboard Down.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Keyboard-azure-pressed.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Keyboard-azure-pressed.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Keyboard-azure-pressed.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Keyboard-azure-pressed.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Keyboard-green-pressed.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Keyboard-green-pressed.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Keyboard-green-pressed.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Keyboard-green-pressed.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Keyboard-grey-pressed.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Keyboard-grey-pressed.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Keyboard-grey-pressed.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Keyboard-grey-pressed.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Keyboard-marine-pressed.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Keyboard-marine-pressed.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Keyboard-marine-pressed.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Keyboard-marine-pressed.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Keyboard-orange-pressed.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Keyboard-orange-pressed.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Keyboard-orange-pressed.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Keyboard-orange-pressed.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Letter Symbol.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Letter Symbol.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Letter Symbol.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Letter Symbol.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Letters Keyboard.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Letters Keyboard.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Letters Keyboard.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Letters Keyboard.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Log Inverted.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Log Inverted.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Log Inverted.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Log Inverted.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Log with base.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Log with base.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Log with base.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Log with base.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Log with base.imageset/Log with base.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Log with base.imageset/Log with base.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Log with base.imageset/Log with base.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Log with base.imageset/Log with base.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Number Symbol.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Number Symbol.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Number Symbol.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Number Symbol.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Numbers Keyboard.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Numbers Keyboard.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Numbers Keyboard.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Numbers Keyboard.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Operations Keyboard.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Operations Keyboard.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Operations Keyboard.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Operations Keyboard.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Operations Symbol.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Operations Symbol.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Operations Symbol.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Operations Symbol.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Shift.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Shift.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Shift.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Shift.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Shift.imageset/Shift.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Shift.imageset/Shift.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Shift.imageset/Shift.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Shift.imageset/Shift.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Sqrt White Fixed.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Sqrt White Fixed.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Sqrt White Fixed.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Sqrt White Fixed.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Sqrt Power Inverted.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Sqrt Power Inverted.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Sqrt Power Inverted.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Sqrt Power Inverted.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Sqrt with Power.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Sqrt with Power.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Sqrt with Power.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Sqrt with Power.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt.imageset/Sqrt.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt.imageset/Sqrt.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt.imageset/Sqrt.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Sqrt.imageset/Sqrt.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Subscript Inverted.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Subscript Inverted.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Subscript Inverted.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Subscript Inverted.pdf
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript.imageset/Contents.json
diff --git a/MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript.imageset/Subscript.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript.imageset/Subscript.pdf
similarity index 100%
rename from MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript.imageset/Subscript.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/NewKeyboardAssets.xcassets/Subscript.imageset/Subscript.pdf
diff --git a/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Contents.json
diff --git a/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Functions Symbol.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Functions Symbol.pdf
similarity index 100%
rename from MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Functions Symbol.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Functions Symbol.pdf
diff --git a/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Contents.json
diff --git a/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Letter Symbol.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Letter Symbol.pdf
similarity index 100%
rename from MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Letter Symbol.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Letter Symbol.pdf
diff --git a/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Contents.json
diff --git a/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Numbers Symbol.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Numbers Symbol.pdf
similarity index 100%
rename from MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Numbers Symbol.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Numbers Symbol.pdf
diff --git a/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Contents.json b/MathEditorObjC/mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Contents.json
similarity index 100%
rename from MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Contents.json
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Contents.json
diff --git a/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Operations Symbol.pdf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Operations Symbol.pdf
similarity index 100%
rename from MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Operations Symbol.pdf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Operations Symbol.pdf
diff --git a/MathKeyboardResources/lmroman10-bolditalic.otf b/MathEditorObjC/mathKeyboard/MathKeyboardResources/lmroman10-bolditalic.otf
similarity index 100%
rename from MathKeyboardResources/lmroman10-bolditalic.otf
rename to MathEditorObjC/mathKeyboard/MathKeyboardResources/lmroman10-bolditalic.otf
diff --git a/MathEditorObjC/mathKeyboard/include/module.modulemap b/MathEditorObjC/mathKeyboard/include/module.modulemap
new file mode 100644
index 0000000..ca67926
--- /dev/null
+++ b/MathEditorObjC/mathKeyboard/include/module.modulemap
@@ -0,0 +1,6 @@
+module MathKeyboard {
+ header "../keyboard/MTMathKeyboardRootView.h"
+ header "../keyboard/MTKeyboard.h"
+
+ export *
+}
diff --git a/mathEditor/keyboard/MTKeyboard.h b/MathEditorObjC/mathKeyboard/keyboard/MTKeyboard.h
similarity index 96%
rename from mathEditor/keyboard/MTKeyboard.h
rename to MathEditorObjC/mathKeyboard/keyboard/MTKeyboard.h
index f96609d..626e63e 100644
--- a/mathEditor/keyboard/MTKeyboard.h
+++ b/MathEditorObjC/mathKeyboard/keyboard/MTKeyboard.h
@@ -8,6 +8,8 @@
// MIT license. See the LICENSE file for details.
//
+#if TARGET_OS_IPHONE
+
#import
#import "MTEditableMathLabel.h"
@@ -25,7 +27,7 @@
@property (weak, nonatomic) IBOutlet UIButton *squareRootButton;
@property (weak, nonatomic) IBOutlet UIButton *radicalButton;
-@property (nonatomic, weak) UIView* textView;
+@property (nonatomic, weak) MTView* textView;
@property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *numbers;
@property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *variables;
@property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *operators;
@@ -63,3 +65,5 @@
- (void) setRadicalState:(BOOL) highlighted;
@end
+
+#endif
diff --git a/mathEditor/keyboard/MTKeyboard.m b/MathEditorObjC/mathKeyboard/keyboard/MTKeyboard.m
similarity index 98%
rename from mathEditor/keyboard/MTKeyboard.m
rename to MathEditorObjC/mathKeyboard/keyboard/MTKeyboard.m
index 7a4b5fa..6c765d3 100644
--- a/mathEditor/keyboard/MTKeyboard.m
+++ b/MathEditorObjC/mathKeyboard/keyboard/MTKeyboard.m
@@ -8,8 +8,9 @@
// MIT license. See the LICENSE file for details.
//
+#if TARGET_OS_IPHONE
+
#import "MTKeyboard.h"
-#import "MTMathKeyboardRootView.h"
#import "MTFontManager.h"
#import "MTMathAtomFactory.h"
@@ -37,7 +38,7 @@ - (NSString*) registerAndGetFontName
static dispatch_once_t once_token;
dispatch_once(&once_token, ^{
- NSBundle *bundle = [MTMathKeyboardRootView getMathKeyboardResourcesBundle];
+ NSBundle *bundle = SWIFTPM_MODULE_BUNDLE;
NSString* fontPath = [bundle pathForResource:@"lmroman10-bolditalic" ofType:@"otf"];
CGDataProviderRef fontDataProvider = CGDataProviderCreateWithFilename([fontPath UTF8String]);
CGFontRef myFont = CGFontCreateWithDataProvider(fontDataProvider);
@@ -273,3 +274,5 @@ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
}
@end
+
+#endif
diff --git a/mathEditor/keyboard/MTMathKeyboardRootView.h b/MathEditorObjC/mathKeyboard/keyboard/MTMathKeyboardRootView.h
similarity index 97%
rename from mathEditor/keyboard/MTMathKeyboardRootView.h
rename to MathEditorObjC/mathKeyboard/keyboard/MTMathKeyboardRootView.h
index e3cae99..3c3dc62 100644
--- a/mathEditor/keyboard/MTMathKeyboardRootView.h
+++ b/MathEditorObjC/mathKeyboard/keyboard/MTMathKeyboardRootView.h
@@ -9,8 +9,9 @@
// MIT license. See the LICENSE file for details.
//
+#if TARGET_OS_IPHONE
+
#import
-#import
#import "MTKeyboard.h"
#import "MTEditableMathLabel.h"
@@ -42,3 +43,5 @@
@property (nonatomic) BOOL radicalHighlighted;
@end
+
+#endif
diff --git a/mathEditor/keyboard/MTMathKeyboardRootView.m b/MathEditorObjC/mathKeyboard/keyboard/MTMathKeyboardRootView.m
similarity index 96%
rename from mathEditor/keyboard/MTMathKeyboardRootView.m
rename to MathEditorObjC/mathKeyboard/keyboard/MTMathKeyboardRootView.m
index cdbb35f..d542560 100644
--- a/mathEditor/keyboard/MTMathKeyboardRootView.m
+++ b/MathEditorObjC/mathKeyboard/keyboard/MTMathKeyboardRootView.m
@@ -9,6 +9,8 @@
// MIT license. See the LICENSE file for details.
//
+#if TARGET_OS_IPHONE
+
#import "MTMathKeyboardRootView.h"
static NSInteger const DEFAULT_KEYBOARD = 0;
@@ -46,7 +48,7 @@ +(instancetype)sharedInstance {
// Gets the math keyboard resources bundle
+(NSBundle *)getMathKeyboardResourcesBundle
{
- return [NSBundle bundleWithURL:[[NSBundle bundleForClass:[self class]] URLForResource:@"MTKeyboardResources" withExtension:@"bundle"]];
+ return SWIFTPM_MODULE_BUNDLE;
}
-(void)awakeFromNib
@@ -212,14 +214,14 @@ - (void)setRadicalHighlighted:(BOOL)radicalHighlighted
}
}
-- (void)startedEditing:(UIView *)label
+- (void)startedEditing:(MTView *)label
{
for (MTKeyboard *keyboard in _keyboards) {
keyboard.textView = label;
}
}
-- (void)finishedEditing:(UIView *)label
+- (void)finishedEditing:(MTView *)label
{
for (MTKeyboard *keyboard in _keyboards) {
keyboard.textView = nil;
@@ -227,3 +229,5 @@ - (void)finishedEditing:(UIView *)label
}
@end
+
+#endif
diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..60098bf
--- /dev/null
+++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.pbxproj
@@ -0,0 +1,429 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 77;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 860CA8B02F7E97620000656F /* MathEditorSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 860CA8AF2F7E97620000656F /* MathEditorSwift */; };
+ 860CA8B22F7E97620000656F /* MathKeyboardSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 860CA8B12F7E97620000656F /* MathKeyboardSwift */; };
+ 86251C312F7E7DC500EAB40F /* MathEditorSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 86251C302F7E7DC500EAB40F /* MathEditorSwift */; };
+ 86251C332F7E7DC500EAB40F /* MathKeyboardSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 86251C322F7E7DC500EAB40F /* MathKeyboardSwift */; };
+ 862F48632F7E9BAA002780E0 /* MathEditorSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 862F48622F7E9BAA002780E0 /* MathEditorSwift */; };
+ 862F48652F7E9BAA002780E0 /* MathKeyboardSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 862F48642F7E9BAA002780E0 /* MathKeyboardSwift */; };
+ 86E17B822F7569EF0079CEC2 /* MathEditorSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 86E17B812F7569EF0079CEC2 /* MathEditorSwift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 862D28E72E2D51E100F9B6FE /* MathEditorSwiftUIExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MathEditorSwiftUIExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFileSystemSynchronizedRootGroup section */
+ 862D28E92E2D51E100F9B6FE /* MathEditorSwiftUIExample */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ path = MathEditorSwiftUIExample;
+ sourceTree = "";
+ };
+/* End PBXFileSystemSynchronizedRootGroup section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 862D28E42E2D51E100F9B6FE /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 862F48652F7E9BAA002780E0 /* MathKeyboardSwift in Frameworks */,
+ 86E17B822F7569EF0079CEC2 /* MathEditorSwift in Frameworks */,
+ 860CA8B22F7E97620000656F /* MathKeyboardSwift in Frameworks */,
+ 860CA8B02F7E97620000656F /* MathEditorSwift in Frameworks */,
+ 862F48632F7E9BAA002780E0 /* MathEditorSwift in Frameworks */,
+ 86251C332F7E7DC500EAB40F /* MathKeyboardSwift in Frameworks */,
+ 86251C312F7E7DC500EAB40F /* MathEditorSwift in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 862D28DE2E2D51E100F9B6FE = {
+ isa = PBXGroup;
+ children = (
+ 862D28E92E2D51E100F9B6FE /* MathEditorSwiftUIExample */,
+ C14D8B922F6CB11E003F79FF /* Frameworks */,
+ 862D28E82E2D51E100F9B6FE /* Products */,
+ );
+ sourceTree = "";
+ };
+ 862D28E82E2D51E100F9B6FE /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 862D28E72E2D51E100F9B6FE /* MathEditorSwiftUIExample.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ C14D8B922F6CB11E003F79FF /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 862D28E62E2D51E100F9B6FE /* MathEditorSwiftUIExample */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 862D28F32E2D51E200F9B6FE /* Build configuration list for PBXNativeTarget "MathEditorSwiftUIExample" */;
+ buildPhases = (
+ 862D28E32E2D51E100F9B6FE /* Sources */,
+ 862D28E42E2D51E100F9B6FE /* Frameworks */,
+ 862D28E52E2D51E100F9B6FE /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ fileSystemSynchronizedGroups = (
+ 862D28E92E2D51E100F9B6FE /* MathEditorSwiftUIExample */,
+ );
+ name = MathEditorSwiftUIExample;
+ packageProductDependencies = (
+ 86E17B812F7569EF0079CEC2 /* MathEditorSwift */,
+ 86251C302F7E7DC500EAB40F /* MathEditorSwift */,
+ 86251C322F7E7DC500EAB40F /* MathKeyboardSwift */,
+ 860CA8AF2F7E97620000656F /* MathEditorSwift */,
+ 860CA8B12F7E97620000656F /* MathKeyboardSwift */,
+ 862F48622F7E9BAA002780E0 /* MathEditorSwift */,
+ 862F48642F7E9BAA002780E0 /* MathKeyboardSwift */,
+ );
+ productName = MathEditorSwiftUIExample;
+ productReference = 862D28E72E2D51E100F9B6FE /* MathEditorSwiftUIExample.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 862D28DF2E2D51E100F9B6FE /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = 1;
+ LastSwiftUpdateCheck = 1640;
+ LastUpgradeCheck = 2630;
+ TargetAttributes = {
+ 862D28E62E2D51E100F9B6FE = {
+ CreatedOnToolsVersion = 16.4;
+ };
+ };
+ };
+ buildConfigurationList = 862D28E22E2D51E100F9B6FE /* Build configuration list for PBXProject "MathEditorSwiftUIExample" */;
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 862D28DE2E2D51E100F9B6FE;
+ minimizedProjectReferenceProxies = 1;
+ packageReferences = (
+ 862F48612F7E9BAA002780E0 /* XCLocalSwiftPackageReference "../../MathEditor" */,
+ );
+ preferredProjectObjectVersion = 77;
+ productRefGroup = 862D28E82E2D51E100F9B6FE /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 862D28E62E2D51E100F9B6FE /* MathEditorSwiftUIExample */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 862D28E52E2D51E100F9B6FE /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 862D28E32E2D51E100F9B6FE /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ 862D28F12E2D51E200F9B6FE /* 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;
+ DEAD_CODE_STRIPPING = YES;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ DEVELOPMENT_TEAM = BT9948YGAL;
+ 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;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ 862D28F22E2D51E200F9B6FE /* 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;
+ DEAD_CODE_STRIPPING = YES;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DEVELOPMENT_TEAM = BT9948YGAL;
+ 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;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ };
+ name = Release;
+ };
+ 862D28F42E2D51E200F9B6FE /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = MathEditorSwiftUIExample/MathEditorSwiftUIExample.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEAD_CODE_STRIPPING = YES;
+ DEVELOPMENT_TEAM = BT9948YGAL;
+ ENABLE_APP_SANDBOX = YES;
+ ENABLE_HARDENED_RUNTIME = YES;
+ ENABLE_PREVIEWS = YES;
+ ENABLE_USER_SELECTED_FILES = readonly;
+ GENERATE_INFOPLIST_FILE = YES;
+ "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
+ "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
+ "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
+ "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
+ "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
+ "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
+ "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
+ "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ IPHONEOS_DEPLOYMENT_TARGET = 17.6;
+ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
+ "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
+ MACOSX_DEPLOYMENT_TARGET = 13.5;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = matheditor.MathEditorSwiftUIExample;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ REGISTER_APP_GROUPS = YES;
+ SDKROOT = auto;
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2,7";
+ XROS_DEPLOYMENT_TARGET = 2.5;
+ };
+ name = Debug;
+ };
+ 862D28F52E2D51E200F9B6FE /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = MathEditorSwiftUIExample/MathEditorSwiftUIExample.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEAD_CODE_STRIPPING = YES;
+ DEVELOPMENT_TEAM = BT9948YGAL;
+ ENABLE_APP_SANDBOX = YES;
+ ENABLE_HARDENED_RUNTIME = YES;
+ ENABLE_PREVIEWS = YES;
+ ENABLE_USER_SELECTED_FILES = readonly;
+ GENERATE_INFOPLIST_FILE = YES;
+ "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
+ "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
+ "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
+ "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
+ "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
+ "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
+ "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
+ "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ IPHONEOS_DEPLOYMENT_TARGET = 17.6;
+ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
+ "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
+ MACOSX_DEPLOYMENT_TARGET = 13.5;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = matheditor.MathEditorSwiftUIExample;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ REGISTER_APP_GROUPS = YES;
+ SDKROOT = auto;
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2,7";
+ XROS_DEPLOYMENT_TARGET = 2.5;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 862D28E22E2D51E100F9B6FE /* Build configuration list for PBXProject "MathEditorSwiftUIExample" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 862D28F12E2D51E200F9B6FE /* Debug */,
+ 862D28F22E2D51E200F9B6FE /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 862D28F32E2D51E200F9B6FE /* Build configuration list for PBXNativeTarget "MathEditorSwiftUIExample" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 862D28F42E2D51E200F9B6FE /* Debug */,
+ 862D28F52E2D51E200F9B6FE /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+
+/* Begin XCLocalSwiftPackageReference section */
+ 862F48612F7E9BAA002780E0 /* XCLocalSwiftPackageReference "../../MathEditor" */ = {
+ isa = XCLocalSwiftPackageReference;
+ relativePath = ../../MathEditor;
+ };
+/* End XCLocalSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ 860CA8AF2F7E97620000656F /* MathEditorSwift */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = MathEditorSwift;
+ };
+ 860CA8B12F7E97620000656F /* MathKeyboardSwift */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = MathKeyboardSwift;
+ };
+ 86251C302F7E7DC500EAB40F /* MathEditorSwift */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = MathEditorSwift;
+ };
+ 86251C322F7E7DC500EAB40F /* MathKeyboardSwift */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = MathKeyboardSwift;
+ };
+ 862F48622F7E9BAA002780E0 /* MathEditorSwift */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = MathEditorSwift;
+ };
+ 862F48642F7E9BAA002780E0 /* MathKeyboardSwift */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = MathKeyboardSwift;
+ };
+ 86E17B812F7569EF0079CEC2 /* MathEditorSwift */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = MathEditorSwift;
+ };
+/* End XCSwiftPackageProductDependency section */
+ };
+ rootObject = 862D28DF2E2D51E100F9B6FE /* Project object */;
+}
diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
new file mode 100644
index 0000000..d643c05
--- /dev/null
+++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -0,0 +1,15 @@
+{
+ "originHash" : "3845228a2da848133f1c5b0fe02201c5631261188fc4a1a940a392278f808293",
+ "pins" : [
+ {
+ "identity" : "iosmath",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/maitbayev/iosMath.git",
+ "state" : {
+ "branch" : "master",
+ "revision" : "bf4a5466fc405031977f2edcf806ccb119c23836"
+ }
+ }
+ ],
+ "version" : 3
+}
diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/Assets.xcassets/AccentColor.colorset/Contents.json b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..eb87897
--- /dev/null
+++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..ffdfe15
--- /dev/null
+++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,85 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "tinted"
+ }
+ ],
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "16x16"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "16x16"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "32x32"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "32x32"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "128x128"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "128x128"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "256x256"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "256x256"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "512x512"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "512x512"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/Assets.xcassets/Contents.json b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift
new file mode 100644
index 0000000..cbb092a
--- /dev/null
+++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/ContentView.swift
@@ -0,0 +1,70 @@
+// Copyright © 2025 Snap, Inc. All rights reserved.
+
+import MathEditorSwift
+import SwiftUI
+
+struct ContentView: View {
+ var body: some View {
+ MathEditorView()
+ .frame(maxHeight: 100)
+ .padding()
+ }
+}
+
+#Preview {
+ ContentView()
+}
+
+#if os(iOS)
+ import MathKeyboardSwift
+
+ struct MathEditorView: UIViewRepresentable {
+ typealias UIViewType = MTEditableMathLabelSwift
+
+ func makeUIView(context: Context) -> MTEditableMathLabelSwift {
+ let mathLabel = MTEditableMathLabelSwift()
+ mathLabel.backgroundColor = .clear
+ mathLabel.keyboard = MTMathKeyboardSwiftRootView.sharedInstance()
+ mathLabel.textColor = .label
+ mathLabel.caretColor = .label
+ mathLabel.startEditing()
+ return mathLabel
+ }
+
+ func updateUIView(_ uiView: MTEditableMathLabelSwift, context: Context) {
+
+ }
+ }
+#endif // os(iOS)
+
+#if os(macOS)
+
+ struct MathEditorView: NSViewRepresentable {
+ typealias UIViewType = NSView
+
+ func makeNSView(context: Context) -> NSView {
+ let mathLabel = MTEditableMathLabelSwift()
+ mathLabel.backgroundColor = .clear
+ mathLabel.caretColor = NSColor.labelColor
+ mathLabel.textColor = NSColor.labelColor
+ // mathLabel.keyboard = MTMathKeyboardRootView.sharedInstance();
+
+ let wrapper = NSView()
+ wrapper.addSubview(mathLabel)
+ mathLabel.translatesAutoresizingMaskIntoConstraints = false
+ NSLayoutConstraint.activate([
+ mathLabel.topAnchor.constraint(equalTo: wrapper.topAnchor),
+ mathLabel.leadingAnchor.constraint(equalTo: wrapper.leadingAnchor),
+ mathLabel.trailingAnchor.constraint(equalTo: wrapper.trailingAnchor),
+ mathLabel.bottomAnchor.constraint(equalTo: wrapper.bottomAnchor, constant: -44),
+ ])
+ wrapper.backgroundColor = .clear
+ return wrapper
+ }
+
+ func updateNSView(_ nsView: NSView, context: Context) {
+
+ }
+ }
+
+#endif
diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/MathEditorSwiftUIExample.entitlements b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/MathEditorSwiftUIExample.entitlements
new file mode 100644
index 0000000..0c67376
--- /dev/null
+++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/MathEditorSwiftUIExample.entitlements
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/MathEditorSwiftUIExample/MathEditorSwiftUIExample/MathEditorSwiftUIExampleApp.swift b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/MathEditorSwiftUIExampleApp.swift
new file mode 100644
index 0000000..d70240a
--- /dev/null
+++ b/MathEditorSwiftUIExample/MathEditorSwiftUIExample/MathEditorSwiftUIExampleApp.swift
@@ -0,0 +1,12 @@
+// Copyright © 2025 Snap, Inc. All rights reserved.
+
+import SwiftUI
+
+@main
+struct MathEditorSwiftUIExampleApp: App {
+ var body: some Scene {
+ WindowGroup {
+ ContentView()
+ }
+ }
+}
diff --git a/Package.resolved b/Package.resolved
new file mode 100644
index 0000000..24b9309
--- /dev/null
+++ b/Package.resolved
@@ -0,0 +1,14 @@
+{
+ "pins" : [
+ {
+ "identity" : "iosmath",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/maitbayev/iosMath.git",
+ "state" : {
+ "branch" : "master",
+ "revision" : "bf4a5466fc405031977f2edcf806ccb119c23836"
+ }
+ }
+ ],
+ "version" : 2
+}
diff --git a/Package.swift b/Package.swift
new file mode 100644
index 0000000..d03abcd
--- /dev/null
+++ b/Package.swift
@@ -0,0 +1,41 @@
+// swift-tools-version: 6.2
+
+import PackageDescription
+
+let package = Package(
+ name: "MathEditor",
+ defaultLocalization: "en",
+ platforms: [.iOS(.v17), .macOS(.v13)],
+ products: [
+ .library(
+ name: "MathEditorSwift",
+ targets: ["MathEditorSwift"]
+ ),
+ .library(
+ name: "MathKeyboardSwift",
+ targets: ["MathKeyboardSwift"]
+ ),
+ ],
+ dependencies: [
+ .package(url: "https://github.com/maitbayev/iosMath.git", branch: "master")
+ ],
+ targets: [
+ .target(
+ name: "MathEditorSwift",
+ dependencies: ["iosMath"]
+ ),
+ .testTarget(
+ name: "MathEditorSwiftTests",
+ dependencies: [
+ "MathEditorSwift"
+ ]
+ ),
+ .target(
+ name: "MathKeyboardSwift",
+ dependencies: [
+ "MathEditorSwift"
+ ],
+ resources: [.process("Resources")]
+ ),
+ ]
+)
diff --git a/Podfile b/Podfile
deleted file mode 100644
index e0a9d88..0000000
--- a/Podfile
+++ /dev/null
@@ -1,13 +0,0 @@
-# Needed due to
-# http://stackoverflow.com/questions/33395675/cocoapods-file-reference-is-a-member-of-multiple-groups
-install! 'cocoapods', :deterministic_uuids => false
-
-target 'MathEditor_Example' do
- pod 'MathEditor', :path => './'
-end
-target 'MathEditor_Tests' do
- pod 'MathEditor', :path => './'
-end
-target 'MathEditor' do
- pod 'iosMath'
-end
diff --git a/Podfile.lock b/Podfile.lock
deleted file mode 100644
index 5f07901..0000000
--- a/Podfile.lock
+++ /dev/null
@@ -1,20 +0,0 @@
-PODS:
- - iosMath (0.9.3)
- - MathEditor (0.2.0):
- - iosMath (~> 0.9.3)
-
-DEPENDENCIES:
- - iosMath
- - MathEditor (from `./`)
-
-EXTERNAL SOURCES:
- MathEditor:
- :path: ./
-
-SPEC CHECKSUMS:
- iosMath: 619e53c34dfa19ac3062869f9974747b44507083
- MathEditor: e93c1344182203e0eafe5c95a6c424a3bbc89bbd
-
-PODFILE CHECKSUM: 824592d1d06afe0a23f07736e4e1f8968e497263
-
-COCOAPODS: 1.1.1
diff --git a/Sources/MathEditorSwift/Internal/DummyTextInputHandler.swift b/Sources/MathEditorSwift/Internal/DummyTextInputHandler.swift
new file mode 100644
index 0000000..5cd2b96
--- /dev/null
+++ b/Sources/MathEditorSwift/Internal/DummyTextInputHandler.swift
@@ -0,0 +1,28 @@
+//
+// MTTextInputHandler.swift
+// MathEditorSwift
+//
+// Created by Madiyar Aitbayev on 26/03/2026.
+//
+
+#if canImport(UIKit)
+
+ import UIKit
+
+ @MainActor
+ struct DummyTextInputHandler {
+ var selectedTextRange: UITextRange?
+ var markedTextRange: UITextRange?
+ var markedTextStyle: [NSAttributedString.Key: Any]?
+ var beginningOfDocument = UITextPosition()
+ var endOfDocument = UITextPosition()
+ var inputDelegate: (any UITextInputDelegate)?
+ var tokenizer: UITextInputTokenizer = UITextInputStringTokenizer()
+ }
+
+#else // canImport(UIKit)
+
+ struct DummyTextInputHandler {
+ }
+
+#endif // canImport(UIKit)
diff --git a/Sources/MathEditorSwift/Internal/MTCancelView.swift b/Sources/MathEditorSwift/Internal/MTCancelView.swift
new file mode 100644
index 0000000..b01609e
--- /dev/null
+++ b/Sources/MathEditorSwift/Internal/MTCancelView.swift
@@ -0,0 +1,34 @@
+import Foundation
+
+final class MTCancelView: MTView {
+ private let imageView: MTImageView
+
+ @objc
+ init(target: AnyObject, action: Selector) {
+ #if canImport(UIKit)
+ let image = MTImage(systemName: "xmark.circle")?.withRenderingMode(.alwaysTemplate)
+ imageView = MTImageView(image: image)
+ imageView.contentMode = .scaleAspectFit
+ imageView.tintColor = .secondaryLabel
+ #else
+ imageView = MTImageView(frame: .zero)
+ imageView.image = MTImage(systemSymbolName: "xmark.circle", accessibilityDescription: nil)
+ imageView.imageScaling = .scaleProportionallyUpOrDown
+ imageView.contentTintColor = .secondaryLabelColor
+ #endif
+
+ super.init(frame: .zero)
+
+ addSubview(imageView)
+ imageView.pinToSuperview()
+
+ addGestureRecognizer(MTTapGestureRecognizer(target: target, action: action))
+
+ isHidden = true
+ }
+
+ @available(*, unavailable)
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+}
diff --git a/Sources/MathEditorSwift/Internal/MTCaretView.swift b/Sources/MathEditorSwift/Internal/MTCaretView.swift
new file mode 100644
index 0000000..5e4742c
--- /dev/null
+++ b/Sources/MathEditorSwift/Internal/MTCaretView.swift
@@ -0,0 +1,300 @@
+import Foundation
+import iosMath
+
+#if canImport(UIKit)
+ import UIKit
+#elseif canImport(AppKit)
+ import AppKit
+#endif
+
+private let initialBlinkDelay: TimeInterval = 0.7
+private let blinkRate: TimeInterval = 0.5
+private let caretFontSize: CGFloat = 30
+private let caretAscent: CGFloat = 25
+private let caretWidth: CGFloat = 3
+private let caretDescent: CGFloat = 7
+private let caretHandleWidth: CGFloat = 15
+private let caretHandleDescent: CGFloat = 8
+private let caretHandleHeight: CGFloat = 20
+private let caretHandleHitAreaSize: CGFloat = 44
+
+// The settings below make sense for the given font size. They are scaled appropriately when the fontsize changes.
+private func caretHeight() -> CGFloat {
+ caretAscent + caretDescent
+}
+
+private final class CaretHandle: MTView {
+ weak var label: MTEditableMathLabelSwift?
+
+ var color: MTColor = MTColor.label {
+ didSet { setNeedsDisplay() }
+ }
+
+ private var path = MTBezierPath()
+ private var isInteracting = false
+
+ private var hitArea: CGRect {
+ // Create a hit area around the center.
+ let size = bounds.size
+ return CGRect(
+ x: (size.width - caretHandleHitAreaSize) / 2,
+ y: (size.height - caretHandleHitAreaSize) / 2,
+ width: caretHandleHitAreaSize,
+ height: caretHandleHitAreaSize
+ )
+ }
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ path = createHandlePath()
+ backgroundColor = .clear
+ }
+
+ @available(*, unavailable)
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func createHandlePath() -> MTBezierPath {
+ let path = MTBezierPath()
+ let size = bounds.size
+ path.move(to: CGPoint(x: size.width / 2, y: 0))
+ path.addLine(to: CGPoint(x: size.width, y: size.height / 4))
+ path.addLine(to: CGPoint(x: size.width, y: size.height))
+ path.addLine(to: CGPoint(x: 0, y: size.height))
+ path.addLine(to: CGPoint(x: 0, y: size.height / 4))
+ path.close()
+ return path
+ }
+
+ private func interactionBegan() {
+ isInteracting = true
+ setNeedsDisplay()
+ }
+
+ private func interactionEnded() {
+ isInteracting = false
+ setNeedsDisplay()
+ }
+
+ private func handleDrag(localPoint: CGPoint) {
+ guard let label else { return }
+ let caretPoint = CGPoint(x: localPoint.x, y: localPoint.y - frame.origin.y)
+ let labelPoint = label.convert(caretPoint, from: self)
+ // puts the point at the top to the top of the current caret
+ label.moveCaret(to: labelPoint)
+ }
+
+ public override func draw(_ rect: CGRect) {
+ let drawColor = color.withAlphaComponent(isInteracting ? 1.0 : 0.6)
+ drawColor.setFill()
+ path.fill()
+ }
+}
+
+#if canImport(UIKit)
+ extension CaretHandle {
+ public override func layoutSubviews() {
+ super.layoutSubviews()
+ path = createHandlePath()
+ }
+
+ public override func touchesBegan(_ touches: Set, with event: UIEvent?) {
+ interactionBegan()
+ }
+
+ public override func touchesCancelled(_ touches: Set, with event: UIEvent?) {
+ interactionEnded()
+ }
+
+ public override func touchesMoved(_ touches: Set, with event: UIEvent?) {
+ guard let touch = touches.first else { return }
+ handleDrag(localPoint: touch.location(in: self))
+ }
+
+ public override func touchesEnded(_ touches: Set, with event: UIEvent?) {
+ interactionEnded()
+ }
+
+ public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
+ self.hitArea.contains(point)
+ }
+ }
+#endif // canImport(UIKit)
+
+#if canImport(AppKit)
+ extension CaretHandle {
+ public override var isFlipped: Bool { true }
+
+ public override func layout() {
+ super.layout()
+ path = createHandlePath()
+ }
+
+ public override func acceptsFirstMouse(for event: NSEvent?) -> Bool {
+ true
+ }
+
+ public override func mouseDown(with event: NSEvent) {
+ interactionBegan()
+ }
+
+ public override func mouseDragged(with event: NSEvent) {
+ handleDrag(localPoint: convert(event.locationInWindow, from: nil))
+ }
+
+ public override func mouseUp(with event: NSEvent) {
+ interactionEnded()
+ }
+
+ public override func mouseCancelled(with event: NSEvent) {
+ interactionEnded()
+ }
+
+ public override func hitTest(_ point: NSPoint) -> NSView? {
+ guard !isHidden else { return nil }
+ let localPoint = convert(point, from: superview)
+ return hitArea.contains(localPoint) ? self : nil
+ }
+ }
+#endif // canImport(AppKit)
+
+final class MTCaretView: MTView {
+ var caretColor: MTColor = MTColor.label {
+ didSet {
+ handle.color = caretColor
+ blinker.backgroundColor = caretColor
+ }
+ }
+
+ private var blinkTimer: Timer?
+ private let blinker = MTView(frame: .zero)
+ private let handle: CaretHandle
+ private var scale: Double
+
+ init(editor: MTEditableMathLabelSwift) {
+ scale = editor.fontSize / caretFontSize
+ handle = CaretHandle(
+ frame: CGRect(
+ x: 0,
+ y: 0,
+ width: caretHandleWidth * scale,
+ height: caretHandleHeight * scale
+ ))
+ super.init(frame: .zero)
+
+ blinker.backgroundColor = caretColor
+ addSubview(blinker)
+
+ handle.color = caretColor
+ handle.isHidden = true
+ handle.label = editor
+ addSubview(handle)
+ }
+
+ @available(*, unavailable)
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ func setPosition(_ position: CGPoint) {
+ // position is in the parent's coordinate system and it is the bottom left corner of the view.
+ frame = CGRect(x: position.x, y: position.y - caretAscent * scale, width: 0, height: 0)
+ }
+
+ func setFontSize(_ fontSize: CGFloat) {
+ scale = fontSize / caretFontSize
+ setNeedsLayout()
+ }
+
+ func showHandle(_ show: Bool) {
+ handle.isHidden = !show
+ }
+
+ // Helper method to set an initial blink delay
+ func delayBlink() {
+ isHidden = false
+ blinker.isHidden = false
+ blinkTimer?.fireDate = Date(timeIntervalSinceNow: initialBlinkDelay)
+ }
+
+ // Helper method to toggle hidden state of caret view.
+ private func blink() {
+ blinker.isHidden.toggle()
+ }
+
+ private func doLayout() {
+ blinker.frame = CGRect(x: 0, y: 0, width: caretWidth * scale, height: caretHeight() * scale)
+ handle.frame = CGRect(
+ x: -((caretHandleWidth - caretWidth) * scale / 2),
+ y: (caretHeight() + caretHandleDescent) * scale,
+ width: caretHandleWidth * scale,
+ height: caretHandleHeight * scale
+ )
+ }
+
+ private func stopBlinking() {
+ blinkTimer?.invalidate()
+ blinkTimer = nil
+ }
+
+ private func startBlinkingIfNeeded() {
+ guard window != nil else {
+ stopBlinking()
+ return
+ }
+ if blinkTimer == nil {
+ blinkTimer = Timer.scheduledTimer(withTimeInterval: blinkRate, repeats: true) {
+ [weak self] _ in
+ Task { @MainActor [weak self] in
+ self?.blink()
+ }
+ }
+ }
+ delayBlink()
+ }
+}
+
+#if canImport(UIKit)
+ extension MTCaretView {
+ public override func didMoveToWindow() {
+ super.didMoveToWindow()
+ // Drive caret blinking from window attachment so ancestor detach/reattach is handled correctly.
+ isHidden = false
+ startBlinkingIfNeeded()
+ }
+
+ public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
+ if !handle.isHidden {
+ return handle.point(inside: convert(point, to: handle), with: event)
+ }
+ return super.point(inside: point, with: event)
+ }
+
+ public override func layoutSubviews() {
+ super.layoutSubviews()
+ doLayout()
+ }
+ }
+#endif // canImport(UIKit)
+
+#if canImport(AppKit)
+ extension MTCaretView {
+ public override var isFlipped: Bool { true }
+
+ public override func viewDidMoveToWindow() {
+ super.viewDidMoveToWindow()
+ isHidden = false
+ startBlinkingIfNeeded()
+ }
+
+ public override func layout() {
+ super.layout()
+ doLayout()
+ }
+
+ public override func hitTest(_ point: NSPoint) -> NSView? {
+ hitTestOutsideBounds(point)
+ }
+ }
+#endif // canImport(AppKit)
diff --git a/Sources/MathEditorSwift/Internal/MTDisplay+Editing.swift b/Sources/MathEditorSwift/Internal/MTDisplay+Editing.swift
new file mode 100644
index 0000000..a1f8779
--- /dev/null
+++ b/Sources/MathEditorSwift/Internal/MTDisplay+Editing.swift
@@ -0,0 +1,462 @@
+//
+// MTDisplay+Editing.swift
+// MathEditor
+//
+// Created by Madiyar Aitbayev on 24/03/2026.
+//
+
+import CoreText
+import Foundation
+import iosMath
+
+private let invalidPosition = CGPoint(x: -1, y: -1)
+// Number of pixels outside the bounds to still consider a point as part of that bounds.
+private let pixelDelta: CGFloat = 2
+
+private func codePointIndexToStringIndex(_ str: String, _ codePointIndex: UInt) -> Int {
+ let utf16 = Array(str.utf16)
+ var codePointCount: UInt = 0
+ var i = 0
+
+ while i < utf16.count {
+ if codePointCount == codePointIndex {
+ return i
+ }
+ codePointCount += 1
+
+ let c = utf16[i]
+ if CFStringIsSurrogateHighCharacter(c) || CFStringIsSurrogateLowCharacter(c) {
+ i += 1
+ }
+ i += 1
+ }
+
+ return NSNotFound
+}
+
+private func distanceFromPointToRect(_ point: CGPoint, _ rect: CGRect) -> CGFloat {
+ // Manhattan distance from the point to the nearest rectangle boundary.
+ var distance: CGFloat = 0
+
+ if point.x < rect.origin.x {
+ distance += rect.origin.x - point.x
+ } else if point.x > rect.maxX {
+ distance += point.x - rect.maxX
+ }
+
+ if point.y < rect.origin.y {
+ distance += rect.origin.y - point.y
+ } else if point.y > rect.maxY {
+ distance += point.y - rect.maxY
+ }
+
+ return distance
+}
+
+extension MTDisplay {
+ // Empty implementations for the base class.
+ @objc(closestIndexToPoint:)
+ public func closestIndex(to point: CGPoint) -> MTMathListIndex? {
+ nil
+ }
+
+ @objc(caretPositionForIndex:)
+ public func caretPosition(for index: MTMathListIndex) -> CGPoint {
+ invalidPosition
+ }
+
+ @objc(highlightCharacterAtIndex:color:)
+ public func highlightCharacter(at index: MTMathListIndex, color: MTColor) {}
+
+ @objc(highlightWithColor:)
+ public func highlight(with color: MTColor) {}
+}
+
+extension MTCTLineDisplay {
+ @objc(closestIndexToPoint:)
+ public override func closestIndex(to point: CGPoint) -> MTMathListIndex? {
+ // Convert the point to the reference of the CTLine.
+ let relativePoint = CGPoint(x: point.x - position.x, y: point.y - position.y)
+ let idx = CTLineGetStringIndexForPosition(line, relativePoint)
+ if idx == kCFNotFound {
+ return nil
+ }
+
+ // The CoreText index is UTF-16 based. Convert to a math-list atom index.
+ let mlIndex = convertToMathListIndex(UInt(idx))
+ assert(mlIndex <= UInt(range.length), "Returned index out of range: \(idx)")
+ return MTMathListIndex.level0Index(UInt(range.location) + mlIndex)
+ }
+
+ @objc(caretPositionForIndex:)
+ public override func caretPosition(for index: MTMathListIndex) -> CGPoint {
+ assert(
+ index.subIndexType == .subIndexTypeNone, "Index in a CTLineDisplay cannot have sub indexes.")
+
+ let offset: CGFloat
+ if Int(index.atomIndex) == NSMaxRange(range) {
+ offset = width
+ } else {
+ assert(NSLocationInRange(Int(index.atomIndex), range), "Index not in range")
+ let strIndex = mathListIndexToStringIndex(index.atomIndex - UInt(range.location))
+ offset = CTLineGetOffsetForStringIndex(line, strIndex, nil)
+ }
+
+ return CGPoint(x: position.x + offset, y: position.y)
+ }
+
+ @objc(highlightCharacterAtIndex:color:)
+ public override func highlightCharacter(at index: MTMathListIndex, color: MTColor) {
+ assert(NSLocationInRange(Int(index.atomIndex), range))
+ assert(index.subIndexType == .subIndexTypeNone || index.subIndexType == .subIndexTypeNucleus)
+
+ if index.subIndexType == .subIndexTypeNucleus {
+ assertionFailure("Nucleus highlighting not supported yet")
+ }
+
+ let charIndex = codePointIndexToStringIndex(
+ attributedString.string, index.atomIndex - UInt(range.location))
+ assert(charIndex != NSNotFound)
+
+ let attrStr = NSMutableAttributedString(attributedString: attributedString)
+ let seqRange = (attrStr.string as NSString).rangeOfComposedCharacterSequence(at: charIndex)
+ attrStr.addAttribute(
+ kCTForegroundColorAttributeName as NSAttributedString.Key,
+ value: color.cgColor,
+ range: seqRange)
+ attributedString = attrStr
+ }
+
+ @objc(highlightWithColor:)
+ public override func highlight(with color: MTColor) {
+ let attrStr = NSMutableAttributedString(attributedString: attributedString)
+ attrStr.addAttribute(
+ kCTForegroundColorAttributeName as NSAttributedString.Key,
+ value: color.cgColor,
+ range: NSRange(location: 0, length: attrStr.length))
+ attributedString = attrStr
+ }
+
+ @objc(convertToMathListIndex:)
+ public func convertToMathListIndex(_ strIndex: UInt) -> UInt {
+ // A single math atom may map to multiple UTF-16 code units.
+ var strLenCovered: UInt = 0
+ for mlIndex in 0..= strIndex {
+ return UInt(mlIndex)
+ }
+ let atom = atoms[mlIndex]
+ strLenCovered += UInt(atom.nucleus.count)
+ }
+ // By the end we should have covered all characters that can be addressed.
+ assert(strLenCovered >= strIndex, "StrIndex should not be more than the len covered")
+ return UInt(atoms.count)
+ }
+
+ @objc(mathListIndexToStringIndex:)
+ public func mathListIndexToStringIndex(_ mlIndex: UInt) -> Int {
+ assert(mlIndex < UInt(atoms.count), "Index not in range")
+
+ var strIndex = 0
+ for i in 0.. MTMathListIndex? {
+ // We can be before the fraction, inside the fraction, or after it.
+ if point.x < position.x - pixelDelta {
+ return MTMathListIndex.level0Index(UInt(range.location))
+ } else if point.x > position.x + width + pixelDelta {
+ return MTMathListIndex.level0Index(UInt(NSMaxRange(range)))
+ } else {
+ let numeratorDistance = distanceFromPointToRect(point, numerator.displayBounds())
+ let denominatorDistance = distanceFromPointToRect(point, denominator.displayBounds())
+ if numeratorDistance < denominatorDistance {
+ return MTMathListIndex(
+ atLocation: UInt(range.location),
+ withSubIndex: numerator.closestIndex(to: point),
+ type: .subIndexTypeNumerator)
+ } else {
+ return MTMathListIndex(
+ atLocation: UInt(range.location),
+ withSubIndex: denominator.closestIndex(to: point),
+ type: .subIndexTypeDenominator)
+ }
+ }
+ }
+
+ @objc(caretPositionForIndex:)
+ public override func caretPosition(for index: MTMathListIndex) -> CGPoint {
+ // Draw a caret before the fraction.
+ assert(index.subIndexType == .subIndexTypeNone)
+ return CGPoint(x: position.x, y: position.y)
+ }
+
+ @objc(highlightCharacterAtIndex:color:)
+ public override func highlightCharacter(at index: MTMathListIndex, color: MTColor) {
+ assert(index.subIndexType == .subIndexTypeNone)
+ highlight(with: color)
+ }
+
+ @objc(highlightWithColor:)
+ public override func highlight(with color: MTColor) {
+ numerator.highlight(with: color)
+ denominator.highlight(with: color)
+ }
+
+ @objc(subAtomForIndexType:)
+ public func subAtom(forIndexType type: MTMathListSubIndexType) -> MTMathListDisplay? {
+ switch type {
+ case .subIndexTypeNumerator:
+ return numerator
+ case .subIndexTypeDenominator:
+ return denominator
+ default:
+ assertionFailure("Not a fraction subtype \(type.rawValue)")
+ return nil
+ }
+ }
+}
+
+extension MTRadicalDisplay {
+ @objc(closestIndexToPoint:)
+ public override func closestIndex(to point: CGPoint) -> MTMathListIndex? {
+ // We can be before the radical, inside the radical, or after it.
+ if point.x < position.x - pixelDelta {
+ return MTMathListIndex.level0Index(UInt(range.location))
+ } else if point.x > position.x + width + pixelDelta {
+ return MTMathListIndex.level0Index(UInt(NSMaxRange(range)))
+ } else {
+ let degreeDistance = distanceFromPointToRect(point, degree!.displayBounds())
+ let radicandDistance = distanceFromPointToRect(point, radicand.displayBounds())
+
+ if degreeDistance < radicandDistance {
+ return MTMathListIndex(
+ atLocation: UInt(range.location),
+ withSubIndex: degree?.closestIndex(to: point),
+ type: .subIndexTypeDegree)
+ } else {
+ return MTMathListIndex(
+ atLocation: UInt(range.location),
+ withSubIndex: radicand.closestIndex(to: point),
+ type: .subIndexTypeRadicand)
+ }
+ }
+ }
+
+ @objc(caretPositionForIndex:)
+ public override func caretPosition(for index: MTMathListIndex) -> CGPoint {
+ // Draw a caret before the radical.
+ assert(index.subIndexType == .subIndexTypeNone)
+ return CGPoint(x: position.x, y: position.y)
+ }
+
+ @objc(highlightCharacterAtIndex:color:)
+ public override func highlightCharacter(at index: MTMathListIndex, color: MTColor) {
+ assert(index.subIndexType == .subIndexTypeNone)
+ highlight(with: color)
+ }
+
+ @objc(highlightWithColor:)
+ public override func highlight(with color: MTColor) {
+ radicand.highlight(with: color)
+ }
+
+ @objc(subAtomForIndexType:)
+ public func subAtom(forIndexType type: MTMathListSubIndexType) -> MTMathListDisplay? {
+ switch type {
+ case .subIndexTypeRadicand:
+ return radicand
+ case .subIndexTypeDegree:
+ return degree
+ default:
+ assertionFailure("Not a radical subtype \(type.rawValue)")
+ return nil
+ }
+ }
+}
+
+extension MTMathListDisplay {
+ @objc(closestIndexToPoint:)
+ public override func closestIndex(to point: CGPoint) -> MTMathListIndex? {
+ // Subdisplay origins are relative to this display's position.
+ let translatedPoint = CGPoint(x: point.x - position.x, y: point.y - position.y)
+
+ var closest: MTDisplay?
+ var xbounds = [MTDisplay]()
+ var minDistance = CGFloat.greatestFiniteMagnitude
+
+ for atom in subDisplays {
+ let bounds = atom.displayBounds()
+ if bounds.origin.x - pixelDelta <= translatedPoint.x
+ && translatedPoint.x <= bounds.maxX + pixelDelta
+ {
+ xbounds.append(atom)
+ }
+
+ let distance = distanceFromPointToRect(translatedPoint, bounds)
+ if distance < minDistance {
+ closest = atom
+ minDistance = distance
+ }
+ }
+
+ let atomWithPoint: MTDisplay?
+ if xbounds.isEmpty {
+ if translatedPoint.x <= -pixelDelta {
+ // Far to the left.
+ return MTMathListIndex.level0Index(UInt(range.location))
+ } else if translatedPoint.x >= width + pixelDelta {
+ // Far to the right.
+ return MTMathListIndex.level0Index(UInt(NSMaxRange(range)))
+ } else {
+ // Within mathlist bounds but not in any x-range; use nearest subdisplay.
+ atomWithPoint = closest
+ }
+ } else if xbounds.count == 1 {
+ atomWithPoint = xbounds[0]
+ if translatedPoint.x >= width - pixelDelta,
+ translatedPoint.y <= atomWithPoint!.displayBounds().minY - pixelDelta
+ {
+ // Near the end but too high for this atom; place caret at end of list.
+ return MTMathListIndex.level0Index(UInt(NSMaxRange(range)))
+ }
+ } else {
+ atomWithPoint = closest
+ }
+
+ guard let atomWithPoint else { return nil }
+
+ let index = atomWithPoint.closestIndex(to: translatedPoint)
+
+ if let closestLine = atomWithPoint as? MTMathListDisplay {
+ assert(
+ closestLine.type == .subscript || closestLine.type == .superscript,
+ "MTLine type regular inside an MTLine - shouldn't happen")
+ // Subscript/superscript line: wrap the returned index as a nested sub-index.
+ let type: MTMathListSubIndexType =
+ (closestLine.type == .subscript) ? .subIndexTypeSubscript : .subIndexTypeSuperscript
+ let lineIndex = Int(closestLine.index)
+ guard lineIndex != NSNotFound else { return nil }
+ return MTMathListIndex(atLocation: UInt(lineIndex), withSubIndex: index, type: type)
+ } else if atomWithPoint.hasScript, let index {
+ // If we landed at atom end, caret should be before scripts, not after them.
+ if Int(index.atomIndex) == NSMaxRange(atomWithPoint.range) {
+ return MTMathListIndex(
+ atLocation: index.atomIndex - 1,
+ withSubIndex: MTMathListIndex.level0Index(1),
+ type: .subIndexTypeNucleus)
+ }
+ }
+
+ return index
+ }
+
+ @objc(subAtomForIndex:)
+ public func subAtom(for index: MTMathListIndex) -> MTDisplay? {
+ // Sub/superscripts are represented as MTMathListDisplay entries in subDisplays.
+ if index.subIndexType == .subIndexTypeSuperscript
+ || index.subIndexType == .subIndexTypeSubscript
+ {
+ for atom in subDisplays {
+ if let lineAtom = atom as? MTMathListDisplay,
+ Int(index.atomIndex) == Int(lineAtom.index)
+ {
+ if (lineAtom.type == .subscript && index.subIndexType == .subIndexTypeSubscript)
+ || (lineAtom.type == .superscript && index.subIndexType == .subIndexTypeSuperscript)
+ {
+ return lineAtom
+ }
+ }
+ }
+ } else {
+ for atom in subDisplays {
+ if !(atom is MTMathListDisplay) && NSLocationInRange(Int(index.atomIndex), atom.range) {
+ // Found the display that covers the requested index.
+ switch index.subIndexType {
+ case .subIndexTypeNone, .subIndexTypeNucleus:
+ return atom
+
+ case .subIndexTypeDegree, .subIndexTypeRadicand:
+ if let radical = atom as? MTRadicalDisplay {
+ return radical.subAtom(forIndexType: index.subIndexType)
+ }
+ return nil
+
+ case .subIndexTypeNumerator, .subIndexTypeDenominator:
+ if let frac = atom as? MTFractionDisplay {
+ return frac.subAtom(forIndexType: index.subIndexType)
+ }
+ return nil
+
+ case .subIndexTypeSubscript, .subIndexTypeSuperscript, .subIndexTypeInner:
+ assertionFailure("Unexpected index type for this path")
+ return nil
+
+ @unknown default:
+ return nil
+ }
+ }
+ }
+ }
+ return nil
+ }
+
+ @objc(caretPositionForIndex:)
+ public override func caretPosition(for index: MTMathListIndex) -> CGPoint {
+ var pos = invalidPosition
+
+ if Int(index.atomIndex) == NSMaxRange(range) {
+ // Special-case right edge of the range.
+ pos = CGPoint(x: width, y: 0)
+ } else if NSLocationInRange(Int(index.atomIndex), range) {
+ guard let atom = subAtom(for: index) else { return invalidPosition }
+ if index.subIndexType == .subIndexTypeNucleus {
+ guard let subIndex = index.sub else { return invalidPosition }
+ let nucleusPosition = index.atomIndex + subIndex.atomIndex
+ pos = atom.caretPosition(for: MTMathListIndex.level0Index(nucleusPosition))
+ } else if index.subIndexType == .subIndexTypeNone {
+ pos = atom.caretPosition(for: index)
+ } else {
+ // Recurse into nested substructures.
+ guard let subIndex = index.sub else { return invalidPosition }
+ pos = atom.caretPosition(for: subIndex)
+ }
+ } else {
+ return invalidPosition
+ }
+
+ if pos == invalidPosition {
+ // Position could not be resolved by subdisplays.
+ return pos
+ }
+
+ // Convert from local coordinates before returning.
+ return CGPoint(x: pos.x + position.x, y: pos.y + position.y)
+ }
+
+ @objc(highlightCharacterAtIndex:color:)
+ public override func highlightCharacter(at index: MTMathListIndex, color: MTColor) {
+ if NSLocationInRange(Int(index.atomIndex), range), let atom = subAtom(for: index) {
+ if index.subIndexType == .subIndexTypeNucleus || index.subIndexType == .subIndexTypeNone {
+ atom.highlightCharacter(at: index, color: color)
+ } else if let subIndex = index.sub {
+ // Recurse into nested substructures.
+ atom.highlightCharacter(at: subIndex, color: color)
+ }
+ }
+ }
+
+ @objc(highlightWithColor:)
+ public override func highlight(with color: MTColor) {
+ for atom in subDisplays {
+ atom.highlight(with: color)
+ }
+ }
+}
diff --git a/Sources/MathEditorSwift/Internal/MTMathList+Editing.swift b/Sources/MathEditorSwift/Internal/MTMathList+Editing.swift
new file mode 100644
index 0000000..4862773
--- /dev/null
+++ b/Sources/MathEditorSwift/Internal/MTMathList+Editing.swift
@@ -0,0 +1,287 @@
+//
+// MTMathList+Editing.swift
+// MathEditor
+//
+// Created by Madiyar Aitbayev on 24/03/2026.
+//
+
+import Foundation
+import iosMath
+
+extension MTMathList {
+ @objc(insertAtom:atListIndex:)
+ public func insert(_ atom: MTMathAtom, atListIndex index: MTMathListIndex) {
+ precondition(
+ index.atomIndex <= UInt(atoms.count),
+ "Index \(index.atomIndex) is out of bounds for list of size \(atoms.count)"
+ )
+ switch index.subIndexType {
+ case .subIndexTypeNone:
+ insertAtom(atom, at: index.atomIndex)
+
+ case .subIndexTypeNucleus:
+ let atomIndex = Int(index.atomIndex)
+ let currentAtom = atoms[atomIndex]
+ assert(
+ currentAtom.subScript != nil || currentAtom.superScript != nil,
+ "Nuclear fusion is not supported if there are no subscripts or superscripts.")
+ assert(
+ atom.subScript == nil && atom.superScript == nil,
+ "Cannot fuse with an atom that already has a subscript or a superscript")
+ guard let subIndex = index.sub else { return }
+
+ atom.subScript = currentAtom.subScript
+ atom.superScript = currentAtom.superScript
+ currentAtom.subScript = nil
+ currentAtom.superScript = nil
+ insertAtom(atom, at: index.atomIndex + subIndex.atomIndex)
+
+ case .subIndexTypeDegree, .subIndexTypeRadicand:
+ let atomIndex = Int(index.atomIndex)
+ guard let radical = atoms[atomIndex] as? MTRadical, radical.type == .radical else {
+ // Not radical, quit.
+ assertionFailure("No radical found at index \(index.atomIndex)")
+ return
+ }
+ guard let subIndex = index.sub else { return }
+ if index.subIndexType == .subIndexTypeDegree {
+ radical.degree?.insert(atom, atListIndex: subIndex)
+ } else {
+ radical.radicand?.insert(atom, atListIndex: subIndex)
+ }
+
+ case .subIndexTypeDenominator, .subIndexTypeNumerator:
+ let atomIndex = Int(index.atomIndex)
+ guard let frac = atoms[atomIndex] as? MTFraction, frac.type == .fraction else {
+ // Not a fraction, quit.
+ assertionFailure("No fraction found at index \(index.atomIndex)")
+ return
+ }
+ guard let subIndex = index.sub else { return }
+ if index.subIndexType == .subIndexTypeNumerator {
+ frac.numerator.insert(atom, atListIndex: subIndex)
+ } else {
+ frac.denominator.insert(atom, atListIndex: subIndex)
+ }
+
+ case .subIndexTypeSubscript:
+ let atomIndex = Int(index.atomIndex)
+ let current = atoms[atomIndex]
+ assert(current.subScript != nil, "No subscript for atom at index \(index.atomIndex)")
+ guard let subIndex = index.sub else { return }
+ current.subScript?.insert(atom, atListIndex: subIndex)
+
+ case .subIndexTypeSuperscript:
+ let atomIndex = Int(index.atomIndex)
+ let current = atoms[atomIndex]
+ assert(current.superScript != nil, "No superscript for atom at index \(index.atomIndex)")
+ guard let subIndex = index.sub else { return }
+ current.superScript?.insert(atom, atListIndex: subIndex)
+
+ case .subIndexTypeInner:
+ break
+
+ @unknown default:
+ break
+ }
+ }
+
+ @objc(removeAtomAtListIndex:)
+ public func removeAtom(atListIndex index: MTMathListIndex) {
+ if index.atomIndex >= UInt(atoms.count) {
+ let exception = NSException(
+ name: .rangeException,
+ reason: "Index \(index.atomIndex) is out of bounds for list of size \(atoms.count)",
+ userInfo: nil
+ )
+ exception.raise()
+ return
+ }
+
+ switch index.subIndexType {
+ case .subIndexTypeNone:
+ removeAtom(at: index.atomIndex)
+
+ case .subIndexTypeNucleus:
+ let atomIndex = Int(index.atomIndex)
+ let currentAtom = atoms[atomIndex]
+ assert(
+ currentAtom.subScript != nil || currentAtom.superScript != nil,
+ "Nuclear fission is not supported if there are no subscripts or superscripts.")
+ var previous: MTMathAtom?
+ if index.atomIndex > 0 {
+ previous = atoms[Int(index.atomIndex - 1)]
+ }
+ if let previous,
+ previous.subScript == nil,
+ previous.superScript == nil
+ {
+ previous.superScript = currentAtom.superScript
+ previous.subScript = currentAtom.subScript
+ removeAtom(at: index.atomIndex)
+ } else {
+ // No previous atom, or the previous atom already has a sub/superscript.
+ currentAtom.nucleus = ""
+ }
+
+ case .subIndexTypeRadicand, .subIndexTypeDegree:
+ let atomIndex = Int(index.atomIndex)
+ guard let radical = atoms[atomIndex] as? MTRadical, radical.type == .radical else {
+ // Not radical, quit.
+ assertionFailure("No radical found at index \(index.atomIndex)")
+ return
+ }
+ guard let subIndex = index.sub else { return }
+ if index.subIndexType == .subIndexTypeDegree {
+ radical.degree?.removeAtom(atListIndex: subIndex)
+ } else {
+ radical.radicand?.removeAtom(atListIndex: subIndex)
+ }
+
+ case .subIndexTypeDenominator, .subIndexTypeNumerator:
+ let atomIndex = Int(index.atomIndex)
+ guard let frac = atoms[atomIndex] as? MTFraction, frac.type == .fraction else {
+ // Not a fraction, quit.
+ assertionFailure("No fraction found at index \(index.atomIndex)")
+ return
+ }
+ guard let subIndex = index.sub else { return }
+ if index.subIndexType == .subIndexTypeNumerator {
+ frac.numerator.removeAtom(atListIndex: subIndex)
+ } else {
+ frac.denominator.removeAtom(atListIndex: subIndex)
+ }
+
+ case .subIndexTypeSubscript:
+ let atomIndex = Int(index.atomIndex)
+ let current = atoms[atomIndex]
+ assert(current.subScript != nil, "No subscript for atom at index \(index.atomIndex)")
+ guard let subIndex = index.sub else { return }
+ current.subScript?.removeAtom(atListIndex: subIndex)
+
+ case .subIndexTypeSuperscript:
+ let atomIndex = Int(index.atomIndex)
+ let current = atoms[atomIndex]
+ assert(current.superScript != nil, "No superscript for atom at index \(index.atomIndex)")
+ guard let subIndex = index.sub else { return }
+ current.superScript?.removeAtom(atListIndex: subIndex)
+
+ case .subIndexTypeInner:
+ break
+
+ @unknown default:
+ break
+ }
+ }
+
+ @objc(removeAtomsInListIndexRange:)
+ public func removeAtoms(inListIndexRange range: MTMathListRange) {
+ let start = range.start
+
+ switch start.subIndexType {
+ case .subIndexTypeNone:
+ removeAtoms(in: NSRange(location: Int(start.atomIndex), length: Int(range.length)))
+
+ case .subIndexTypeNucleus:
+ assertionFailure("Nuclear fission is not supported")
+
+ case .subIndexTypeRadicand, .subIndexTypeDegree:
+ let atomIndex = Int(start.atomIndex)
+ guard let radical = atoms[atomIndex] as? MTRadical, radical.type == .radical else {
+ // Not radical, quit.
+ assertionFailure("No radical found at index \(start.atomIndex)")
+ return
+ }
+ guard let subIndexRange = range.subIndex() else { return }
+ if start.subIndexType == .subIndexTypeDegree {
+ radical.degree?.removeAtoms(inListIndexRange: subIndexRange)
+ } else {
+ radical.radicand?.removeAtoms(inListIndexRange: subIndexRange)
+ }
+
+ case .subIndexTypeDenominator, .subIndexTypeNumerator:
+ let atomIndex = Int(start.atomIndex)
+ guard let frac = atoms[atomIndex] as? MTFraction, frac.type == .fraction else {
+ // Not a fraction, quit.
+ assertionFailure("No fraction found at index \(start.atomIndex)")
+ return
+ }
+ guard let subIndexRange = range.subIndex() else { return }
+ if start.subIndexType == .subIndexTypeNumerator {
+ frac.numerator.removeAtoms(inListIndexRange: subIndexRange)
+ } else {
+ frac.denominator.removeAtoms(inListIndexRange: subIndexRange)
+ }
+
+ case .subIndexTypeSubscript:
+ let atomIndex = Int(start.atomIndex)
+ let current = atoms[atomIndex]
+ assert(current.subScript != nil, "No subscript for atom at index \(start.atomIndex)")
+ guard let subIndexRange = range.subIndex() else { return }
+ current.subScript?.removeAtoms(inListIndexRange: subIndexRange)
+
+ case .subIndexTypeSuperscript:
+ let atomIndex = Int(start.atomIndex)
+ let current = atoms[atomIndex]
+ assert(current.superScript != nil, "No superscript for atom at index \(start.atomIndex)")
+ guard let subIndexRange = range.subIndex() else { return }
+ current.superScript?.removeAtoms(inListIndexRange: subIndexRange)
+
+ case .subIndexTypeInner:
+ break
+
+ @unknown default:
+ break
+ }
+ }
+
+ @objc(atomAtListIndex:)
+ public func atom(atListIndex index: MTMathListIndex?) -> MTMathAtom? {
+ guard let index else { return nil }
+ guard index.atomIndex < UInt(atoms.count) else { return nil }
+ let atom = atoms[Int(index.atomIndex)]
+
+ switch index.subIndexType {
+ case .subIndexTypeNone, .subIndexTypeNucleus:
+ return atom
+
+ case .subIndexTypeSubscript:
+ guard let subIndex = index.sub else { return nil }
+ return atom.subScript?.atom(atListIndex: subIndex)
+
+ case .subIndexTypeSuperscript:
+ guard let subIndex = index.sub else { return nil }
+ return atom.superScript?.atom(atListIndex: subIndex)
+
+ case .subIndexTypeRadicand, .subIndexTypeDegree:
+ guard let radical = atom as? MTRadical, atom.type == .radical else {
+ // No radical at this index.
+ return nil
+ }
+ guard let subIndex = index.sub else { return nil }
+ if index.subIndexType == .subIndexTypeDegree {
+ return radical.degree?.atom(atListIndex: subIndex)
+ } else {
+ return radical.radicand?.atom(atListIndex: subIndex)
+ }
+
+ case .subIndexTypeNumerator, .subIndexTypeDenominator:
+ guard let frac = atom as? MTFraction, atom.type == .fraction else {
+ // No fraction at this index.
+ return nil
+ }
+ guard let subIndex = index.sub else { return nil }
+ if index.subIndexType == .subIndexTypeDenominator {
+ return frac.denominator.atom(atListIndex: subIndex)
+ } else {
+ return frac.numerator.atom(atListIndex: subIndex)
+ }
+
+ case .subIndexTypeInner:
+ return nil
+
+ @unknown default:
+ return nil
+ }
+ }
+}
diff --git a/Sources/MathEditorSwift/Internal/MTTapGestureRecognizer.swift b/Sources/MathEditorSwift/Internal/MTTapGestureRecognizer.swift
new file mode 100644
index 0000000..61594b1
--- /dev/null
+++ b/Sources/MathEditorSwift/Internal/MTTapGestureRecognizer.swift
@@ -0,0 +1,9 @@
+import Foundation
+
+#if canImport(UIKit)
+ import UIKit
+ typealias MTTapGestureRecognizer = UITapGestureRecognizer
+#elseif canImport(AppKit)
+ import AppKit
+ typealias MTTapGestureRecognizer = NSClickGestureRecognizer
+#endif
diff --git a/Sources/MathEditorSwift/Internal/MTView/MTView+AutoLayout.swift b/Sources/MathEditorSwift/Internal/MTView/MTView+AutoLayout.swift
new file mode 100644
index 0000000..4f54b8b
--- /dev/null
+++ b/Sources/MathEditorSwift/Internal/MTView/MTView+AutoLayout.swift
@@ -0,0 +1,30 @@
+//
+// MTView+AutoLayout.swift
+// MathEditor
+//
+// Created by Madiyar Aitbayev on 24/03/2026.
+//
+
+import Foundation
+
+#if canImport(AppKit)
+ import AppKit
+#elseif canImport(UIKit)
+ import UIKit
+#endif
+
+extension MTView {
+ public func pinToSuperview(
+ top: Double = 0, leading: Double = 0, bottom: Double = 0, trailing: Double = 0
+ ) {
+ guard let superview else { return }
+ translatesAutoresizingMaskIntoConstraints = false
+
+ NSLayoutConstraint.activate([
+ topAnchor.constraint(equalTo: superview.topAnchor, constant: top),
+ leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: leading),
+ trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: -trailing),
+ bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: -bottom),
+ ])
+ }
+}
diff --git a/Sources/MathEditorSwift/Internal/MTView/MTView.swift b/Sources/MathEditorSwift/Internal/MTView/MTView.swift
new file mode 100644
index 0000000..690469a
--- /dev/null
+++ b/Sources/MathEditorSwift/Internal/MTView/MTView.swift
@@ -0,0 +1,26 @@
+//
+// MTView.swift
+// MathEditor
+//
+// Created by Madiyar Aitbayev on 24/03/2026.
+//
+
+import Foundation
+
+#if canImport(UIKit)
+ import UIKit
+ public typealias MTView = UIView
+ public typealias MTColor = UIColor
+ public typealias MTBezierPath = UIBezierPath
+ public typealias MTImage = UIImage
+ public typealias MTImageView = UIImageView
+ public typealias MTEdgeInsets = UIEdgeInsets
+#elseif canImport(AppKit)
+ import AppKit
+ public typealias MTView = NSView
+ public typealias MTColor = NSColor
+ public typealias MTBezierPath = NSBezierPath
+ public typealias MTImage = NSImage
+ public typealias MTImageView = NSImageView
+ public typealias MTEdgeInsets = NSEdgeInsets
+#endif
diff --git a/Sources/MathEditorSwift/Internal/MTView/NSColor+label.swift b/Sources/MathEditorSwift/Internal/MTView/NSColor+label.swift
new file mode 100644
index 0000000..bc7cf97
--- /dev/null
+++ b/Sources/MathEditorSwift/Internal/MTView/NSColor+label.swift
@@ -0,0 +1,17 @@
+//
+// NSColor+labelColor.swift
+// MathEditorSwift
+//
+// Created by Madiyar Aitbayev on 26/03/2026.
+//
+
+#if canImport(AppKit)
+ import AppKit
+
+ extension NSColor {
+ static var label: NSColor {
+ NSColor.labelColor
+ }
+ }
+
+#endif // canImport(AppKit)
diff --git a/Sources/MathEditorSwift/Internal/MTView/NSView+FirstResponder.swift b/Sources/MathEditorSwift/Internal/MTView/NSView+FirstResponder.swift
new file mode 100644
index 0000000..e913b48
--- /dev/null
+++ b/Sources/MathEditorSwift/Internal/MTView/NSView+FirstResponder.swift
@@ -0,0 +1,18 @@
+//
+// MTView+FirstResponder.swift
+// MathEditor
+//
+// Created by Madiyar Aitbayev on 24/03/2026.
+//
+
+#if os(macOS)
+
+ import AppKit
+
+ extension NSView {
+ @objc var isFirstResponder: Bool {
+ window?.firstResponder == self
+ }
+ }
+
+#endif
diff --git a/Sources/MathEditorSwift/Internal/MTView/NSView+HitTest.swift b/Sources/MathEditorSwift/Internal/MTView/NSView+HitTest.swift
new file mode 100644
index 0000000..2f8a988
--- /dev/null
+++ b/Sources/MathEditorSwift/Internal/MTView/NSView+HitTest.swift
@@ -0,0 +1,37 @@
+//
+// MTView+HitTest.swift
+// MathEditor
+//
+// Created by Madiyar Aitbayev on 24/03/2026.
+//
+
+#if canImport(AppKit)
+ import AppKit
+ extension NSView {
+ @objc func hitTestOutsideBounds(_ point: NSPoint) -> NSView? {
+ hitTestOutsideBounds(point, ignoringSubviews: [])
+ }
+
+ @objc func hitTestOutsideBounds(_ point: NSPoint, ignoringSubviews: [NSView]) -> NSView? {
+ if isHidden {
+ return nil
+ }
+
+ let localPoint = convert(point, from: superview)
+ for child in subviews.reversed() {
+ if ignoringSubviews.contains(child) {
+ continue
+ }
+ if let hitView = child.hitTest(localPoint) {
+ return hitView
+ }
+ }
+
+ if bounds.contains(localPoint) {
+ return self
+ }
+
+ return nil
+ }
+ }
+#endif
diff --git a/Sources/MathEditorSwift/Internal/MTView/NSView+Layout.swift b/Sources/MathEditorSwift/Internal/MTView/NSView+Layout.swift
new file mode 100644
index 0000000..39c5a33
--- /dev/null
+++ b/Sources/MathEditorSwift/Internal/MTView/NSView+Layout.swift
@@ -0,0 +1,30 @@
+//
+// NSView+Layout.swift
+// MathEditor
+//
+// Created by Madiyar Aitbayev on 24/03/2026.
+//
+
+#if canImport(AppKit)
+ import AppKit
+
+ extension NSView {
+ func setNeedsLayout() {
+ self.needsDisplay = true
+ }
+
+ func setNeedsDisplay() {
+ self.needsDisplay = true
+ }
+
+ func layoutIfNeeded() {
+ layoutSubtreeIfNeeded()
+ }
+
+ func bringSubviewToFront(_ child: NSView) {
+ guard child.superview == self else { return }
+ child.removeFromSuperview()
+ addSubview(child)
+ }
+ }
+#endif
diff --git a/Sources/MathEditorSwift/MTEditableMathLabelSwift+NSView.swift b/Sources/MathEditorSwift/MTEditableMathLabelSwift+NSView.swift
new file mode 100644
index 0000000..ddf00c3
--- /dev/null
+++ b/Sources/MathEditorSwift/MTEditableMathLabelSwift+NSView.swift
@@ -0,0 +1,18 @@
+import Foundation
+
+#if canImport(AppKit)
+ import AppKit
+
+ extension MTEditableMathLabelSwift {
+ public override func layout() {
+ super.layout()
+ doLayout()
+ }
+
+ public override var isFlipped: Bool { true }
+
+ public override func hitTest(_ point: NSPoint) -> NSView? {
+ hitTestOutsideBounds(point, ignoringSubviews: [label])
+ }
+ }
+#endif
diff --git a/Sources/MathEditorSwift/MTEditableMathLabelSwift+Responder.swift b/Sources/MathEditorSwift/MTEditableMathLabelSwift+Responder.swift
new file mode 100644
index 0000000..f133f10
--- /dev/null
+++ b/Sources/MathEditorSwift/MTEditableMathLabelSwift+Responder.swift
@@ -0,0 +1,51 @@
+import Foundation
+
+#if canImport(UIKit)
+ import UIKit
+#endif
+#if canImport(AppKit)
+ import AppKit
+#endif
+
+extension MTEditableMathLabelSwift {
+ public override func becomeFirstResponder() -> Bool {
+ let didBecome = super.becomeFirstResponder()
+ if didBecome {
+ doBecomeFirstResponder()
+ }
+ return didBecome
+ }
+
+ public override func resignFirstResponder() -> Bool {
+ guard isFirstResponder else { return true }
+ let didResign = super.resignFirstResponder()
+ doResignFirstResponder()
+ return didResign
+ }
+}
+
+#if canImport(UIKit)
+ extension MTEditableMathLabelSwift {
+ public override var inputView: UIView? {
+ keyboard as? UIView
+ }
+
+ public override var canBecomeFirstResponder: Bool { true }
+ }
+#endif
+
+#if canImport(AppKit)
+ extension MTEditableMathLabelSwift {
+ public override var acceptsFirstResponder: Bool { true }
+
+ public override func keyDown(with event: NSEvent) {
+ // interpretKeyEvents feeds the event into the input system,
+ // which calls insertText: or deleteBackward: as appropriate.
+ interpretKeyEvents([event])
+ }
+
+ public override func deleteBackward(_ sender: Any?) {
+ deleteBackward()
+ }
+ }
+#endif
diff --git a/Sources/MathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift b/Sources/MathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift
new file mode 100644
index 0000000..d1dc6c2
--- /dev/null
+++ b/Sources/MathEditorSwift/MTEditableMathLabelSwift+UITextInput.swift
@@ -0,0 +1,179 @@
+import Foundation
+
+#if canImport(UIKit)
+ import ObjectiveC
+ import UIKit
+
+ // These are blank just to get a UITextInput implementation, to fix the dictation button bug.
+ // Proposed fix from: http://stackoverflow.com/questions/20980898/work-around-for-dictation-custom-text-view-bug
+
+ extension MTEditableMathLabelSwift: UITextInput {
+ public var selectedTextRange: UITextRange? {
+ get {
+ textInputHandler.selectedTextRange
+ }
+ set {
+ textInputHandler.selectedTextRange = newValue
+ }
+ }
+
+ public var inputDelegate: UITextInputDelegate? {
+ get {
+ textInputHandler.inputDelegate
+ }
+ set {
+ textInputHandler.inputDelegate = newValue
+ }
+ }
+
+ public var markedTextRange: UITextRange? {
+ textInputHandler.markedTextRange
+ }
+
+ public var markedTextStyle: [NSAttributedString.Key: Any]? {
+ get {
+ textInputHandler.markedTextStyle
+ }
+ set {
+ textInputHandler.markedTextStyle = newValue
+ }
+ }
+
+ public var beginningOfDocument: UITextPosition {
+ textInputHandler.beginningOfDocument
+ }
+
+ public var endOfDocument: UITextPosition {
+ textInputHandler.endOfDocument
+ }
+
+ public var tokenizer: UITextInputTokenizer {
+ textInputHandler.tokenizer
+ }
+
+ public func baseWritingDirection(
+ for position: UITextPosition,
+ in direction: UITextStorageDirection
+ ) -> NSWritingDirection {
+ .leftToRight
+ }
+
+ public func caretRect(for position: UITextPosition) -> CGRect {
+ .zero
+ }
+
+ public func unmarkText() {}
+
+ public func characterRange(at point: CGPoint) -> UITextRange? {
+ nil
+ }
+
+ public func characterRange(
+ byExtending position: UITextPosition,
+ in direction: UITextLayoutDirection
+ ) -> UITextRange? {
+ nil
+ }
+
+ public func closestPosition(to point: CGPoint) -> UITextPosition? {
+ nil
+ }
+
+ public func closestPosition(to point: CGPoint, within range: UITextRange) -> UITextPosition? {
+ nil
+ }
+
+ public func compare(_ position: UITextPosition, to other: UITextPosition) -> ComparisonResult {
+ .orderedSame
+ }
+
+ public func dictationRecognitionFailed() {}
+
+ public func dictationRecordingDidEnd() {}
+
+ public func firstRect(for range: UITextRange) -> CGRect {
+ .zero
+ }
+
+ public func frame(
+ forDictationResultPlaceholder placeholder: Any
+ ) -> CGRect {
+ .zero
+ }
+
+ public func insertDictationResult(_ dictationResult: [UIDictationPhrase]) {}
+
+ public var insertDictationResultPlaceholder: Any { 0 }
+
+ public func offset(from: UITextPosition, to toPosition: UITextPosition) -> Int {
+ 0
+ }
+
+ public func position(
+ from position: UITextPosition,
+ in direction: UITextLayoutDirection,
+ offset: Int
+ ) -> UITextPosition? {
+ nil
+ }
+
+ public func position(from position: UITextPosition, offset: Int) -> UITextPosition? {
+ nil
+ }
+
+ public func position(
+ within range: UITextRange,
+ farthestIn direction: UITextLayoutDirection
+ ) -> UITextPosition? {
+ nil
+ }
+
+ public func removeDictationResultPlaceholder(
+ _ placeholder: Any,
+ willInsertResult: Bool
+ ) {}
+
+ public func replace(_ range: UITextRange, withText text: String) {}
+
+ public func selectionRects(for range: UITextRange) -> [UITextSelectionRect] {
+ []
+ }
+
+ public func setBaseWritingDirection(
+ _ writingDirection: NSWritingDirection,
+ for range: UITextRange
+ ) {}
+
+ public func setMarkedText(_ markedText: String?, selectedRange: NSRange) {}
+
+ public func text(in range: UITextRange) -> String? {
+ nil
+ }
+
+ public func textRange(from fromPosition: UITextPosition, to toPosition: UITextPosition)
+ -> UITextRange?
+ {
+ nil
+ }
+
+ public var autocapitalizationType: UITextAutocapitalizationType {
+ .none
+ }
+
+ public var autocorrectionType: UITextAutocorrectionType {
+ .no
+ }
+
+ public var returnKeyType: UIReturnKeyType {
+ .default
+ }
+
+ public var spellCheckingType: UITextSpellCheckingType {
+ .no
+ }
+
+ public var keyboardType: UIKeyboardType {
+ .asciiCapable
+ }
+ }
+#endif
diff --git a/Sources/MathEditorSwift/MTEditableMathLabelSwift+UIView.swift b/Sources/MathEditorSwift/MTEditableMathLabelSwift+UIView.swift
new file mode 100644
index 0000000..961660c
--- /dev/null
+++ b/Sources/MathEditorSwift/MTEditableMathLabelSwift+UIView.swift
@@ -0,0 +1,19 @@
+import Foundation
+
+#if canImport(UIKit)
+ import UIKit
+
+ extension MTEditableMathLabelSwift {
+ public override func layoutSubviews() {
+ super.layoutSubviews()
+ doLayout()
+ }
+
+ public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
+ if super.point(inside: point, with: event) {
+ return true
+ }
+ return caretView.point(inside: convert(point, to: caretView), with: event)
+ }
+ }
+#endif
diff --git a/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift b/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift
new file mode 100644
index 0000000..8c7461c
--- /dev/null
+++ b/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift
@@ -0,0 +1,777 @@
+import Foundation
+import iosMath
+
+private let greekLowerStart: UInt32 = 0x03B1
+private let greekLowerEnd: UInt32 = 0x03C9
+private let greekCapitalStart: UInt32 = 0x0391
+private let greekCapitalEnd: UInt32 = 0x03A9
+
+/// Delegate for the `MTEditableMathLabel`. All methods are optional.
+public protocol MTEditableMathLabelDelegate: AnyObject {
+ func returnPressed(_ label: MTEditableMathLabelSwift)
+ func textModified(_ label: MTEditableMathLabelSwift)
+ func didBeginEditing(_ label: MTEditableMathLabelSwift)
+ func didEndEditing(_ label: MTEditableMathLabelSwift)
+}
+
+extension MTEditableMathLabelDelegate {
+ public func returnPressed(_ label: MTEditableMathLabelSwift) {}
+ public func textModified(_ label: MTEditableMathLabelSwift) {}
+ public func didBeginEditing(_ label: MTEditableMathLabelSwift) {}
+ public func didEndEditing(_ label: MTEditableMathLabelSwift) {}
+}
+
+/// This protocol provides information on the context of the current insertion point.
+/// The keyboard may choose to enable/disable/highlight certain parts of the UI depending on the context.
+/// e.g. you cannot enter the = sign when you are in a fraction so the keyboard could disable that.
+@MainActor
+public protocol MTMathKeyboardTraits {
+ var equalsAllowed: Bool { get set }
+ var fractionsAllowed: Bool { get set }
+ var variablesAllowed: Bool { get set }
+ var numbersAllowed: Bool { get set }
+ var operatorsAllowed: Bool { get set }
+ var exponentHighlighted: Bool { get set }
+ var squareRootHighlighted: Bool { get set }
+ var radicalHighlighted: Bool { get set }
+}
+
+/// Any keyboard that provides input to the `MTEditableMathUILabel` must implement
+/// this protocol.
+///
+/// This protocol informs the keyboard when a particular `MTEditableMathUILabel` is being edited.
+/// The keyboard should use this information to send `MTKeyInput` messages to the label.
+///
+/// This protocol inherits from `MTMathKeyboardTraits`.
+@MainActor
+public protocol MTMathKeyboard: AnyObject, MTMathKeyboardTraits {
+ func startedEditing(_ label: MTView & MTKeyInput)
+ func finishedEditing(_ label: MTView & MTKeyInput)
+}
+
+public final class MTEditableMathLabelSwift: MTView, MTKeyInput {
+ @objc public var mathList: MTMathList = MTMathList() {
+ didSet {
+ label.mathList = mathList
+ insertionIndex = MTMathListIndex.level0Index(UInt(mathList.atoms.count))
+ insertionPointChanged()
+ }
+ }
+
+ @objc public var highlightColor: MTColor = .systemRed
+
+ @objc public var textColor: MTColor {
+ get { label.textColor }
+ set { label.textColor = newValue }
+ }
+
+ @objc public var caretColor: MTColor {
+ get { caretView.caretColor }
+ set { caretView.caretColor = newValue }
+ }
+
+ @objc private var cancelImage: MTCancelView?
+ @objc private(set) var caretView: MTCaretView!
+ public weak var delegate: MTEditableMathLabelDelegate?
+ public weak var keyboard: MTMathKeyboard?
+
+ @objc public var fontSize: CGFloat {
+ get { label.fontSize }
+ set {
+ label.fontSize = newValue
+ caretView.setFontSize(newValue)
+ insertionPointChanged()
+ }
+ }
+
+ @objc public var contentInsets: MTEdgeInsets {
+ get { label.contentInsets }
+ set { label.contentInsets = newValue }
+ }
+
+ let label = MTMathUILabel(frame: .zero)
+ private var tapGestureRecognizer: MTTapGestureRecognizer?
+ private var insertionIndex: MTMathListIndex?
+ private var flipTransform = CGAffineTransform.identity
+
+ var textInputHandler = DummyTextInputHandler()
+
+ public override init(frame: CGRect) {
+ super.init(frame: frame)
+ initialize()
+ }
+
+ @available(*, unavailable)
+ public required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ public override var backgroundColor: MTColor? {
+ didSet {
+ label.backgroundColor = backgroundColor
+ }
+ }
+
+ @objc public func clear() {
+ mathList = MTMathList()
+ caretView.showHandle(false)
+ }
+
+ @objc(highlightCharacterAtIndex:)
+ public func highlightCharacter(at index: MTMathListIndex) {
+ label.layoutIfNeeded()
+ guard let displayList = label.displayList else { return }
+ displayList.highlightCharacter(at: index, color: highlightColor)
+ label.setNeedsDisplay()
+ }
+
+ @objc public func clearHighlights() {
+ label.setNeedsLayout()
+ }
+
+ @objc(moveCaretToPoint:)
+ public func moveCaret(to point: CGPoint) {
+ insertionIndex = closestIndex(to: point)
+ insertionPointChanged()
+ }
+
+ @objc public func startEditing() {
+ guard !isFirstResponder else { return }
+ #if canImport(AppKit)
+ window?.makeFirstResponder(self)
+ #else
+ becomeFirstResponder()
+ #endif
+ }
+
+ @objc(enableTap:)
+ public func enableTap(_ enabled: Bool) {
+ tapGestureRecognizer?.isEnabled = enabled
+ }
+
+ @objc(insertMathList:atPoint:)
+ public func insertMathList(_ list: MTMathList, at point: CGPoint) {
+ // insert at the given index - but don't consider sublevels at this point
+ let detailedIndex = closestIndex(to: point) ?? .level0Index(0)
+ var index = MTMathListIndex.level0Index(detailedIndex.atomIndex)
+ for atom in list.atoms {
+ mathList.insert(atom, atListIndex: index)
+ index = index.next()
+ }
+ label.mathList = mathList
+ insertionIndex = index
+ insertionPointChanged()
+ }
+
+ @objc public func mathDisplaySize() -> CGSize {
+ label.sizeThatFits(label.bounds.size)
+ }
+
+ @objc public func doLayout() {
+ cancelImage?.frame = CGRect(
+ x: frame.size.width - 55, y: (frame.size.height - 45) / 2, width: 45, height: 45)
+ // update the flip transform
+ let transform = CGAffineTransform(translationX: 0, y: bounds.size.height)
+ flipTransform = CGAffineTransform(scaleX: 1, y: -1).concatenating(transform)
+ label.layoutIfNeeded()
+ insertionPointChanged()
+ }
+
+ @objc public func doBecomeFirstResponder() {
+ insertionIndex = resolvedInsertionIndex()
+ keyboard?.startedEditing(self)
+ insertionPointChanged()
+ delegate?.didBeginEditing(self)
+ }
+
+ @objc public func doResignFirstResponder() {
+ keyboard?.finishedEditing(self)
+ insertionPointChanged()
+ delegate?.didEndEditing(self)
+ }
+
+ @objc(insertText:)
+ public func insertText(_ string: String) {
+ if string == "\n" {
+ delegate?.returnPressed(self)
+ return
+ }
+
+ guard !string.isEmpty else { return }
+ let scalar = string.unicodeScalars.first!
+ var insertedAtom: MTMathAtom?
+
+ if string.count > 1 {
+ // Check if this is a supported command
+ insertedAtom = MTMathAtomFactory.atom(forLatexSymbolName: string)
+ } else {
+ insertedAtom = atom(forCharacter: scalar)
+ }
+
+ if insertionIndex?.subIndexType == .subIndexTypeDenominator, insertedAtom?.type == .relation {
+ insertionIndex = insertionIndex?.levelDown()?.next()
+ }
+
+ switch string {
+ case String(Character("^")):
+ handleExponentButton()
+ case MTSymbolSquareRoot:
+ handleRadical(withDegreeButtonPressed: false)
+ case MTSymbolCubeRoot:
+ handleRadical(withDegreeButtonPressed: true)
+ case String(Character("_")):
+ handleSubscriptButton()
+ case String(Character("/")):
+ handleSlashButton()
+ case "()":
+ removePlaceholderIfPresent()
+ insertParens()
+ case "||":
+ removePlaceholderIfPresent()
+ insertAbsValue()
+ default:
+ let insertionIndex = resolvedInsertionIndex()
+ if let insertedAtom {
+ if !updatePlaceholderIfPresent(insertedAtom) {
+ // If a placeholder wasn't updated then insert the new element.
+ mathList.insert(insertedAtom, atListIndex: insertionIndex)
+ }
+ if insertedAtom.type == .fraction {
+ // go to the numerator
+ self.insertionIndex = insertionIndex.levelUp(
+ withSubIndex: MTMathListIndex.level0Index(0), type: .subIndexTypeNumerator)
+ } else {
+ self.insertionIndex = insertionIndex.next()
+ }
+ }
+ }
+
+ label.mathList = mathList
+ insertionPointChanged()
+
+ // If trig function, insert parens after
+ if isTrigFunction(string) {
+ insertParens()
+ }
+
+ delegate?.textModified(self)
+ }
+
+ @objc public func deleteBackward() {
+ guard hasText, var previousIndex = insertionIndex?.previous() else { return }
+
+ // delete the last atom from the list
+ mathList.removeAtom(atListIndex: previousIndex)
+ if previousIndex.finalSubIndexType() == MTMathListSubIndexType.subIndexTypeNucleus,
+ let downIndex = previousIndex.levelDown()
+ {
+ if let previous = downIndex.previous() {
+ previousIndex = previous.levelUp(
+ withSubIndex: MTMathListIndex.level0Index(1),
+ type: MTMathListSubIndexType.subIndexTypeNucleus)
+ } else {
+ previousIndex = downIndex
+ }
+ }
+ insertionIndex = previousIndex
+
+ if insertionIndex?.isAtBeginningOfLine() == true,
+ insertionIndex?.subIndexType != .subIndexTypeNone
+ {
+ // We have deleted to the beginning of the line and it is not the outermost line
+ if mathList.atom(atListIndex: insertionIndex) == nil, let insertionIndex {
+ // add a placeholder if we deleted everything in the list
+ let atom = MTMathAtomFactory.placeholder()
+ // mark the placeholder as selected since that is the current insertion point.
+ atom.nucleus = MTSymbolBlackSquare
+ mathList.insert(atom, atListIndex: insertionIndex)
+ }
+ }
+
+ label.mathList = mathList
+ if mathList.atoms.isEmpty {
+ caretView.showHandle(false)
+ }
+ insertionPointChanged()
+ delegate?.textModified(self)
+ }
+
+ @objc public var hasText: Bool {
+ !mathList.atoms.isEmpty
+ }
+
+ @objc(closestIndexToPoint:)
+ public func closestIndex(to point: CGPoint) -> MTMathListIndex? {
+ label.layoutIfNeeded()
+ // no mathlist, so can't figure it out.
+ guard let displayList = label.displayList else { return nil }
+ return displayList.closestIndex(to: convert(point, to: label))
+ }
+
+ @objc(caretRectForIndex:)
+ public func caretRect(for index: MTMathListIndex) -> CGPoint {
+ label.layoutIfNeeded()
+ // no mathlist so we can't figure it out.
+ guard let displayList = label.displayList else { return .zero }
+ return displayList.caretPosition(for: index)
+ }
+}
+
+extension MTEditableMathLabelSwift {
+ fileprivate func initialize() {
+ // Add tap gesture recognizer to let the user enter editing mode.
+ let tap = MTTapGestureRecognizer(target: self, action: #selector(tap(_:)))
+ addGestureRecognizer(tap)
+ tapGestureRecognizer = tap
+
+ // Create and set up the APLSimpleCoreTextView that will do the drawing.
+ addSubview(label)
+ label.pinToSuperview()
+ label.fontSize = 30
+ label.backgroundColor = backgroundColor
+ #if canImport(UIKit)
+ label.isUserInteractionEnabled = false
+ #endif
+ label.textAlignment = .center
+
+ let transform = CGAffineTransform(translationX: 0, y: bounds.size.height)
+ flipTransform = CGAffineTransform(scaleX: 1, y: -1).concatenating(transform)
+
+ caretView = MTCaretView(editor: self)
+ caretView.caretColor = MTColor(white: 0.1, alpha: 1.0)
+
+ highlightColor = MTColor.systemRed
+
+ // createCancelImage()
+
+ // start with an empty math list
+ mathList = MTMathList()
+ }
+
+ fileprivate func createCancelImage() {
+ guard cancelImage == nil else { return }
+ let cancelImage = MTCancelView(target: self, action: #selector(clear))
+ cancelImage.frame = CGRect(
+ x: frame.size.width - 55, y: (frame.size.height - 45) / 2, width: 45, height: 45)
+ addSubview(cancelImage)
+ self.cancelImage = cancelImage
+ }
+
+ @objc fileprivate func tap(_ tap: MTTapGestureRecognizer) {
+ handleTap(at: tap.location(in: self))
+ }
+
+ fileprivate func handleTap(at point: CGPoint) {
+ if !isFirstResponder {
+ insertionIndex = nil
+ caretView.showHandle(false)
+ startEditing()
+ return
+ }
+
+ // If already editing move the cursor and show handle
+ insertionIndex =
+ closestIndex(to: point) ?? MTMathListIndex.level0Index(UInt(mathList.atoms.count))
+ caretView.showHandle(true)
+ insertionPointChanged()
+ }
+
+ fileprivate static func clearPlaceholders(in mathList: MTMathList?) {
+ guard let mathList else { return }
+ for atom in mathList.atoms {
+ if atom.type == .placeholder {
+ atom.nucleus = MTSymbolWhiteSquare
+ }
+ if atom.superScript != nil {
+ clearPlaceholders(in: atom.superScript)
+ }
+ if atom.subScript != nil {
+ clearPlaceholders(in: atom.subScript)
+ }
+ if atom.type == .radical, let radical = atom as? MTRadical {
+ clearPlaceholders(in: radical.degree)
+ clearPlaceholders(in: radical.radicand)
+ }
+ if atom.type == .fraction, let fraction = atom as? MTFraction {
+ clearPlaceholders(in: fraction.numerator)
+ clearPlaceholders(in: fraction.denominator)
+ }
+ }
+ }
+
+ // Helper method to update caretView when insertion point/selection changes.
+ fileprivate func insertionPointChanged() {
+ // If not in editing mode, we don't show the caret.
+ guard isFirstResponder else {
+ caretView.removeFromSuperview()
+ cancelImage?.isHidden = true
+ return
+ }
+
+ Self.clearPlaceholders(in: mathList)
+
+ if let index = insertionIndex, let atom = mathList.atom(atListIndex: index),
+ atom.type == .placeholder
+ {
+ atom.nucleus = MTSymbolBlackSquare
+ if index.finalSubIndexType() == .subIndexTypeNucleus {
+ // If the insertion index is inside a placeholder, move it out.
+ insertionIndex = index.levelDown()
+ }
+ } else if let previousIndex = insertionIndex?.previous(),
+ let atom = mathList.atom(atListIndex: previousIndex),
+ atom.type == .placeholder,
+ atom.superScript == nil,
+ atom.subScript == nil
+ {
+ insertionIndex = previousIndex
+ atom.nucleus = MTSymbolBlackSquare
+ }
+
+ setKeyboardMode()
+
+ // Find the insert point rect and create a caretView to draw the caret at this position.
+ guard let insertionIndex else { return }
+ let caretPosition = caretRect(for: insertionIndex)
+ // Check tht we were returned a valid position before displaying a caret there.
+ guard caretPosition != CGPoint(x: -1, y: -1) else { return }
+
+ // caretFrame is in the flipped coordinate system, flip it back
+ caretView.setPosition(caretPosition.applying(flipTransform))
+ if caretView.superview == nil {
+ addSubview(caretView)
+ setNeedsDisplay()
+ }
+
+ // when a caret is displayed, the X symbol should be as well
+ cancelImage?.isHidden = false
+ // Set up a timer to "blink" the caret.
+ caretView.delayBlink()
+ label.setNeedsLayout()
+ }
+
+ fileprivate func setKeyboardMode() {
+ keyboard?.exponentHighlighted = false
+ keyboard?.radicalHighlighted = false
+ keyboard?.squareRootHighlighted = false
+ keyboard?.equalsAllowed = true
+
+ if insertionIndex?.hasSubIndex(of: .subIndexTypeSuperscript) == true {
+ keyboard?.exponentHighlighted = true
+ keyboard?.equalsAllowed = false
+ }
+ if insertionIndex?.subIndexType == .subIndexTypeNumerator {
+ keyboard?.equalsAllowed = false
+ } else if insertionIndex?.subIndexType == .subIndexTypeDenominator {
+ keyboard?.equalsAllowed = false
+ }
+
+ if insertionIndex?.subIndexType == .subIndexTypeDegree {
+ keyboard?.radicalHighlighted = true
+ } else if insertionIndex?.subIndexType == .subIndexTypeRadicand {
+ keyboard?.squareRootHighlighted = true
+ }
+ }
+
+ fileprivate func atom(forCharacter scalar: UnicodeScalar) -> MTMathAtom? {
+ let string = String(scalar)
+ // Get the basic conversion from MTMathAtomFactory, and then special case
+ // unicode characters and latex special characters.
+ if let atom = MTMathAtomFactory.atom(forCharacter: UInt16(scalar.value)) {
+ return atom
+ }
+ switch string {
+ case MTSymbolMultiplication:
+ return MTMathAtomFactory.times()
+ case MTSymbolSquareRoot:
+ return MTMathAtomFactory.placeholderSquareRoot()
+ case MTSymbolInfinity, MTSymbolDegree, MTSymbolAngle:
+ return MTMathAtom(type: .ordinary, value: string)
+ case MTSymbolDivision:
+ return MTMathAtomFactory.divide()
+ case MTSymbolFractionSlash:
+ return MTMathAtomFactory.placeholderFraction()
+ case "{":
+ return MTMathAtom(type: .open, value: string)
+ case "}":
+ return MTMathAtom(type: .close, value: string)
+ case MTSymbolGreaterEqual, MTSymbolLessEqual:
+ return MTMathAtom(type: .relation, value: string)
+ case "*":
+ return MTMathAtomFactory.times()
+ case "/":
+ return MTMathAtomFactory.divide()
+ default:
+ break
+ }
+
+ let value = scalar.value
+ if (greekLowerStart...greekLowerEnd).contains(value)
+ || (greekCapitalStart...greekCapitalEnd).contains(value)
+ {
+ // All greek chars are rendered as variables.
+ return MTMathAtom(type: .variable, value: string)
+ }
+ if value < 0x21 || value > 0x7E || string == "'" || string == "~" {
+ // not ascii
+ return nil
+ }
+ // just an ordinary character
+ return MTMathAtom(type: .ordinary, value: string)
+ }
+
+ fileprivate func handleExponentButton() {
+ handleScriptButton(.subIndexTypeSuperscript)
+ }
+
+ fileprivate func handleSubscriptButton() {
+ handleScriptButton(.subIndexTypeSubscript)
+ }
+
+ private func resolvedInsertionIndex() -> MTMathListIndex {
+ insertionIndex ?? MTMathListIndex.level0Index(UInt(mathList.atoms.count))
+ }
+
+ fileprivate func handleScriptButton(_ type: MTMathListSubIndexType) {
+ let insertionIndex = resolvedInsertionIndex()
+ if insertionIndex.hasSubIndex(of: type) {
+ // The index is currently inside a script. The button gets it out of the script and move forward.
+ self.insertionIndex = getIndexAfterSpecialStructure(insertionIndex, type: type)
+ return
+ }
+
+ if !insertionIndex.isAtBeginningOfLine(),
+ let atom = mathList.atom(atListIndex: insertionIndex.previous())
+ {
+ let hadScript = scriptList(for: atom, type: type) != nil
+ let count = ensureScriptList(for: atom, type: type).atoms.count
+ if !hadScript {
+ self.insertionIndex = insertionIndex.previous()?.levelUp(
+ withSubIndex: MTMathListIndex.level0Index(0), type: type)
+ } else if insertionIndex.finalSubIndexType() == .subIndexTypeNucleus {
+ // If we are already inside the nucleus, then we come out and go up to the script
+ self.insertionIndex = insertionIndex.levelDown()?.levelUp(
+ withSubIndex: MTMathListIndex.level0Index(UInt(count)), type: type)
+ } else {
+ self.insertionIndex = insertionIndex.previous()?.levelUp(
+ withSubIndex: MTMathListIndex.level0Index(UInt(count)), type: type)
+ }
+ return
+ }
+
+ let emptyAtom = MTMathAtomFactory.placeholder()
+ setScriptList(makePlaceholderMathList(), on: emptyAtom, type: type)
+ if !updatePlaceholderIfPresent(emptyAtom) {
+ // If the placeholder hasn't been updated then insert it.
+ mathList.insert(emptyAtom, atListIndex: insertionIndex)
+ }
+ self.insertionIndex = insertionIndex.levelUp(
+ withSubIndex: MTMathListIndex.level0Index(0), type: type)
+ }
+
+ fileprivate func getIndexAfterSpecialStructure(
+ _ index: MTMathListIndex, type: MTMathListSubIndexType
+ ) -> MTMathListIndex {
+ var nextIndex = index
+ while nextIndex.hasSubIndex(of: type) {
+ nextIndex = nextIndex.levelDown() ?? nextIndex
+ }
+ //Point to just after this node.
+ return nextIndex.next()
+ }
+
+ fileprivate func handleSlashButton() {
+ let insertionIndex = resolvedInsertionIndex()
+ // special / handling - makes the thing a fraction
+ let numerator = MTMathList()
+ var current = insertionIndex
+ while !current.isAtBeginningOfLine() {
+ guard let atom = mathList.atom(atListIndex: current.previous()) else { break }
+ if atom.type != .number && atom.type != .variable {
+ // we don't put this atom on the fraction
+ break
+ }
+ // add the number to the beginning of the list
+ numerator.insert(atom, atListIndex: MTMathListIndex.level0Index(0))
+ current = current.previous()!
+ }
+
+ if current.atomIndex == insertionIndex.atomIndex {
+ // so we didn't really find any numbers before this, so make the numerator 1
+ if let atom = atom(forCharacter: "1".unicodeScalars.first!) {
+ numerator.addAtom(atom)
+ }
+ if !current.isAtBeginningOfLine(),
+ let previousAtom = mathList.atom(atListIndex: current.previous()),
+ previousAtom.type == .fraction
+ {
+ let times = MTMathAtomFactory.times()
+ // add a times symbol
+ mathList.insert(times, atListIndex: current)
+ current = current.next()
+ }
+ } else {
+ // delete stuff in the mathlist from current to _insertionIndex
+ mathList.removeAtoms(
+ inListIndexRange: MTMathListRange.make(
+ current, length: insertionIndex.atomIndex - current.atomIndex))
+ }
+
+ let fraction = MTFraction()
+ fraction.denominator = MTMathList()
+ fraction.denominator.addAtom(MTMathAtomFactory.placeholder())
+ fraction.numerator = numerator
+ // insert it
+ mathList.insert(fraction, atListIndex: current)
+ // update the insertion index to go the denominator
+ self.insertionIndex = current.levelUp(
+ withSubIndex: MTMathListIndex.level0Index(0), type: .subIndexTypeDenominator)
+ }
+
+ fileprivate func getOutOfRadical(_ index: MTMathListIndex) -> MTMathListIndex {
+ var index = index
+ if index.hasSubIndex(of: .subIndexTypeDegree) {
+ index = getIndexAfterSpecialStructure(index, type: .subIndexTypeDegree)
+ }
+ if index.hasSubIndex(of: .subIndexTypeRadicand) {
+ index = getIndexAfterSpecialStructure(index, type: .subIndexTypeRadicand)
+ }
+ return index
+ }
+
+ fileprivate func handleRadical(withDegreeButtonPressed: Bool) {
+ let current = resolvedInsertionIndex()
+
+ if current.hasSubIndex(of: .subIndexTypeDegree)
+ || current.hasSubIndex(of: .subIndexTypeRadicand),
+ let radical = mathList.atoms[Int(current.atomIndex)] as? MTRadical
+ {
+ if withDegreeButtonPressed {
+ if radical.degree == nil {
+ radical.degree = MTMathList()
+ radical.degree?.addAtom(MTMathAtomFactory.placeholder())
+ insertionIndex = current.levelDown()?.levelUp(
+ withSubIndex: MTMathListIndex.level0Index(0), type: .subIndexTypeDegree)
+ } else if current.hasSubIndex(of: .subIndexTypeRadicand) {
+ // If the cursor is at the radicand, switch it to the degree
+ insertionIndex = current.levelDown()?.levelUp(
+ withSubIndex: MTMathListIndex.level0Index(0), type: .subIndexTypeDegree)
+ } else {
+ // If the cursor is at the degree, get out of the radical
+ insertionIndex = getOutOfRadical(current)
+ }
+ } else if current.hasSubIndex(of: .subIndexTypeDegree) {
+ // If the radical the cursor at has a degree, and the cursor is at the degree, move the cursor to the radicand.
+ insertionIndex = current.levelDown()?.levelUp(
+ withSubIndex: MTMathListIndex.level0Index(0), type: .subIndexTypeRadicand)
+ } else {
+ // If the cursor is at the radicand, get out of the radical.
+ insertionIndex = getOutOfRadical(current)
+ }
+ return
+ }
+
+ let radical =
+ withDegreeButtonPressed
+ ? MTMathAtomFactory.placeholderRadical() : MTMathAtomFactory.placeholderSquareRoot()
+ mathList.insert(radical, atListIndex: current)
+ insertionIndex = current.levelUp(
+ withSubIndex: MTMathListIndex.level0Index(0),
+ type: withDegreeButtonPressed ? .subIndexTypeDegree : .subIndexTypeRadicand
+ )
+ }
+
+ fileprivate func removePlaceholderIfPresent() {
+ guard let insertionIndex, let current = mathList.atom(atListIndex: insertionIndex),
+ current.type == .placeholder
+ else { return }
+ // remove this element - the inserted text replaces the placeholder
+ mathList.removeAtom(atListIndex: insertionIndex)
+ }
+
+ // Returns true if updated
+ fileprivate func updatePlaceholderIfPresent(_ atom: MTMathAtom) -> Bool {
+ guard let insertionIndex,
+ let current = mathList.atom(atListIndex: insertionIndex),
+ current.type == .placeholder
+ else { return false }
+ if let superScript = current.superScript {
+ atom.superScript = superScript
+ }
+ if let subScript = current.subScript {
+ atom.subScript = subScript
+ }
+ // remove the placeholder and replace with atom.
+ mathList.removeAtom(atListIndex: insertionIndex)
+ mathList.insert(atom, atListIndex: insertionIndex)
+ return true
+ }
+
+ // Return YES if string is a trig function, otherwise return NO
+ fileprivate func isTrigFunction(_ string: String) -> Bool {
+ ["sin", "cos", "tan", "sec", "csc", "cot"].contains(string)
+ }
+
+ fileprivate func insertParens() {
+ insertPairedAtoms(open: "(", close: ")")
+ }
+
+ fileprivate func insertAbsValue() {
+ insertPairedAtoms(open: "|", close: "|")
+ }
+
+ private func insertPairedAtoms(open: Character, close: Character) {
+ guard let openAtom = atom(forCharacter: open.unicodeScalars.first!),
+ let closeAtom = atom(forCharacter: close.unicodeScalars.first!)
+ else { return }
+ let insertionIndex = resolvedInsertionIndex()
+ mathList.insert(openAtom, atListIndex: insertionIndex)
+ self.insertionIndex = insertionIndex.next()
+ if let insertionIndex = self.insertionIndex {
+ mathList.insert(closeAtom, atListIndex: insertionIndex)
+ }
+ // Don't go to the next insertion index, to start inserting before the close parens.
+ }
+
+ fileprivate func makePlaceholderMathList() -> MTMathList {
+ let list = MTMathList()
+ list.addAtom(MTMathAtomFactory.placeholder())
+ return list
+ }
+
+ fileprivate func scriptList(for atom: MTMathAtom, type: MTMathListSubIndexType) -> MTMathList? {
+ switch type {
+ case .subIndexTypeSuperscript:
+ atom.superScript
+ case .subIndexTypeSubscript:
+ atom.subScript
+ default:
+ nil
+ }
+ }
+
+ fileprivate func setScriptList(
+ _ list: MTMathList?, on atom: MTMathAtom, type: MTMathListSubIndexType
+ ) {
+ switch type {
+ case .subIndexTypeSuperscript:
+ atom.superScript = list
+ case .subIndexTypeSubscript:
+ atom.subScript = list
+ default:
+ break
+ }
+ }
+
+ @discardableResult
+ fileprivate func ensureScriptList(for atom: MTMathAtom, type: MTMathListSubIndexType)
+ -> MTMathList
+ {
+ if let list = scriptList(for: atom, type: type) {
+ return list
+ }
+ let list = makePlaceholderMathList()
+ setScriptList(list, on: atom, type: type)
+ return list
+ }
+}
diff --git a/Sources/MathEditorSwift/MTKeyInput.swift b/Sources/MathEditorSwift/MTKeyInput.swift
new file mode 100644
index 0000000..05bf242
--- /dev/null
+++ b/Sources/MathEditorSwift/MTKeyInput.swift
@@ -0,0 +1,18 @@
+//
+// MTKeyInput.swift
+// MathEditorSwift
+//
+// Created by Madiyar Aitbayev on 26/03/2026.
+//
+
+#if os(iOS)
+ import UIKit
+ public typealias MTKeyInput = UIKeyInput
+#else
+ @MainActor
+ public protocol MTKeyInput {
+ var hasText: Bool { get }
+ func insertText(_ text: String)
+ func deleteBackward()
+ }
+#endif
diff --git a/Sources/MathKeyboardSwift/KeyboardContainerView.swift b/Sources/MathKeyboardSwift/KeyboardContainerView.swift
new file mode 100644
index 0000000..e8df39a
--- /dev/null
+++ b/Sources/MathKeyboardSwift/KeyboardContainerView.swift
@@ -0,0 +1,36 @@
+//
+// KeyboardContainerView.swift
+// MathEditor
+//
+// Created by Madiyar Aitbayev on 22/03/2026.
+//
+
+import SwiftUI
+
+struct KeyboardContainerView: View {
+ let state: KeyboardState
+ let onAction: (KeyboardAction) -> Void
+
+ var body: some View {
+ keyboardView(for: state.currentTab)
+ }
+
+ @ViewBuilder
+ private func keyboardView(for tab: KeyboardTab) -> some View {
+ switch tab {
+ case .numbers:
+ NumbersKeyboardView(state: state, onAction: onAction)
+ case .operations:
+ OperationsKeyboardView(state: state, onAction: onAction)
+ case .functions:
+ FunctionsKeyboardView(state: state, onAction: onAction)
+ case .letters:
+ LettersKeyboardView(
+ state: state,
+ isLowercase: state.isLowercase,
+ onShift: { onAction(.toggleShift) },
+ onAction: onAction
+ )
+ }
+ }
+}
diff --git a/Sources/MathKeyboardSwift/KeyboardState.swift b/Sources/MathKeyboardSwift/KeyboardState.swift
new file mode 100644
index 0000000..c65492a
--- /dev/null
+++ b/Sources/MathKeyboardSwift/KeyboardState.swift
@@ -0,0 +1,19 @@
+//
+// KeyboardState.swift
+// MathEditor
+//
+// Created by Madiyar Aitbayev on 22/03/2026.
+//
+
+struct KeyboardState: Equatable {
+ var currentTab: KeyboardTab = .numbers
+ var isLowercase = true
+ var equalsAllowed = true
+ var fractionsAllowed = true
+ var variablesAllowed = true
+ var numbersAllowed = true
+ var operatorsAllowed = true
+ var exponentHighlighted = false
+ var squareRootHighlighted = false
+ var radicalHighlighted = false
+}
diff --git a/Sources/MathKeyboardSwift/KeyboardTab.swift b/Sources/MathKeyboardSwift/KeyboardTab.swift
new file mode 100644
index 0000000..f197cdf
--- /dev/null
+++ b/Sources/MathKeyboardSwift/KeyboardTab.swift
@@ -0,0 +1,26 @@
+//
+// KeyboardTab.swift
+// MathEditor
+//
+// Created by Madiyar Aitbayev on 22/03/2026.
+//
+
+enum KeyboardTab: CaseIterable, Hashable, Equatable, Identifiable {
+ case numbers
+ case operations
+ case functions
+ case letters
+
+ var id: Self { self }
+}
+
+extension KeyboardTab {
+ var imageNames: (normal: String, selected: String) {
+ switch self {
+ case .numbers: ("Numbers Symbol wbg", "Number Symbol")
+ case .operations: ("Operations Symbol wbg", "Operations Symbol")
+ case .functions: ("Functions Symbol wbg", "Functions Symbol")
+ case .letters: ("Letter Symbol wbg", "Letter Symbol")
+ }
+ }
+}
diff --git a/Sources/MathKeyboardSwift/Keyboards/FunctionsKeyboardView.swift b/Sources/MathKeyboardSwift/Keyboards/FunctionsKeyboardView.swift
new file mode 100644
index 0000000..db3b18c
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Keyboards/FunctionsKeyboardView.swift
@@ -0,0 +1,106 @@
+import Foundation
+import MathEditorSwift
+import SwiftUI
+import iosMath
+
+struct FunctionsKeyboardView: View {
+ let state: KeyboardState
+ let onAction: (KeyboardAction) -> Void
+
+ var body: some View {
+ MainKeyboardView(
+ backgroundImageName: "Functions Keyboard",
+ middleColumns: makeFunctionsGrid(state: state, onAction: onAction),
+ state: state,
+ onAction: onAction
+ )
+ }
+}
+
+private func makeFunctionsGrid(
+ state: KeyboardState,
+ onAction: @escaping (KeyboardAction) -> Void
+) -> [[KeyboardCell]] {
+ let trigLeftItems: [KeyboardCell] = [
+ .text(
+ label: "sin", tone: .dark, action: { onAction(.insertText("sin")) }, enabled: true,
+ pressedAsset: "Keyboard-green-pressed"),
+ .text(
+ label: "sec", tone: .dark, action: { onAction(.insertText("sec")) }, enabled: true,
+ pressedAsset: "Keyboard-green-pressed"),
+ .text(
+ label: "log", tone: .dark, action: { onAction(.insertText("log")) }, enabled: true,
+ pressedAsset: "Keyboard-green-pressed"),
+ .image(
+ imageName: "Subscript",
+ action: { onAction(.insertText("_")) },
+ enabled: true,
+ accessibilityLabel: "Subscript",
+ pressedAsset: "Keyboard-green-pressed"),
+ ]
+
+ let trigMiddleItems: [KeyboardCell] = [
+ .text(
+ label: "cos", tone: .dark, action: { onAction(.insertText("cos")) }, enabled: true,
+ pressedAsset: "Keyboard-green-pressed"),
+ .text(
+ label: "csc", tone: .dark, action: { onAction(.insertText("csc")) }, enabled: true,
+ pressedAsset: "Keyboard-green-pressed"),
+ .text(
+ label: "ln", tone: .dark, action: { onAction(.insertText("ln")) }, enabled: true,
+ pressedAsset: "Keyboard-green-pressed"),
+ .image(
+ imageName: "Sqrt",
+ action: { onAction(.insertText(MTSymbolSquareRoot)) },
+ enabled: true,
+ accessibilityLabel: "Square root",
+ pressedAsset: "Keyboard-green-pressed",
+ overlayAsset: state.squareRootHighlighted ? "Keyboard-green-pressed" : nil),
+ ]
+
+ let trigRightItems: [KeyboardCell] = [
+ .text(
+ label: "tan", tone: .dark, action: { onAction(.insertText("tan")) }, enabled: true,
+ pressedAsset: "Keyboard-green-pressed"),
+ .text(
+ label: "cot", tone: .dark, action: { onAction(.insertText("cot")) }, enabled: true,
+ pressedAsset: "Keyboard-green-pressed"),
+ .image(
+ imageName: "Log with base",
+ action: {
+ onAction(.insertText("log"))
+ onAction(.insertText("_"))
+ },
+ enabled: true,
+ accessibilityLabel: "Log with base",
+ pressedAsset: "Keyboard-green-pressed"),
+ .image(
+ imageName: "Sqrt with Power",
+ action: { onAction(.insertText(MTSymbolCubeRoot)) },
+ enabled: true,
+ accessibilityLabel: "Root with power",
+ pressedAsset: "Keyboard-green-pressed",
+ overlayAsset: state.radicalHighlighted ? "Keyboard-green-pressed" : nil),
+ ]
+
+ let constantsItems: [KeyboardCell] = [
+ .text(
+ label: "θ", tone: .dark, fontName: "TimesNewRomanPSMT",
+ action: { onAction(.insertText("θ")) }, enabled: true,
+ pressedAsset: "Keyboard-green-pressed"),
+ .text(
+ label: "π", tone: .dark, fontName: "TimesNewRomanPSMT",
+ action: { onAction(.insertText("π")) }, enabled: true,
+ pressedAsset: "Keyboard-green-pressed"),
+ .text(
+ label: "∠", tone: .dark, fontName: "Apple SD Gothic Neo",
+ action: { onAction(.insertText("∠")) }, enabled: true,
+ pressedAsset: "Keyboard-green-pressed"),
+ .text(
+ label: "°", tone: .dark, fontName: "HelveticaNeue-ThinItalic",
+ action: { onAction(.insertText("°")) }, enabled: true,
+ pressedAsset: "Keyboard-green-pressed"),
+ ]
+
+ return [trigLeftItems, trigMiddleItems, trigRightItems, constantsItems]
+}
diff --git a/Sources/MathKeyboardSwift/Keyboards/KeyButton.swift b/Sources/MathKeyboardSwift/Keyboards/KeyButton.swift
new file mode 100644
index 0000000..afd6355
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Keyboards/KeyButton.swift
@@ -0,0 +1,73 @@
+//
+// KeyButton.swift
+// MathKeyboardSwift
+//
+// Created by Madiyar Aitbayev on 23/03/2026.
+//
+
+import SwiftUI
+
+struct KeyButton: View {
+ var cell: KeyboardCell
+
+ init(_ cell: KeyboardCell) {
+ self.cell = cell
+ }
+
+ var body: some View {
+ Button(action: cell.action) {
+ ZStack {
+ Rectangle().fill(Color.white.opacity(0.001))
+ if let overlayAsset = cell.overlayAsset {
+ Image(overlayAsset, bundle: .module)
+ .resizable()
+ }
+ switch cell.content {
+ case .text(let text):
+ Text(text.value)
+ .font(.custom(text.fontName, size: text.fontSize))
+ .foregroundColor(textColor(for: text.tone))
+ .padding(cell.padding)
+ case .image(let image):
+ Image(image.name, bundle: .module)
+ .renderingMode(.original)
+ .scaledToFill()
+ .padding(cell.padding)
+ }
+ }
+ }
+ .buttonStyle(
+ KeyboardPressStyle(pressedAsset: cell.pressedAsset)
+ )
+ .disabled(!cell.enabled)
+ .opacity(cell.enabled ? 1 : 0.75)
+ .accessibilityLabel(cell.accessibilityLabel)
+ }
+}
+
+private func textColor(for tone: KeyboardCell.TextTone) -> Color {
+ switch tone {
+ case .light: .white
+ case .dark: .black
+ case .disabled: Color(white: 0.67)
+ }
+}
+
+private struct KeyboardPressStyle: ButtonStyle {
+ let pressedAsset: String?
+
+ func makeBody(configuration: Configuration) -> some View {
+ ZStack {
+ if configuration.isPressed {
+ if let pressedAsset {
+ Image(pressedAsset, bundle: .module)
+ .resizable()
+ .opacity(1.0)
+ } else {
+ Color.clear
+ }
+ }
+ configuration.label
+ }
+ }
+}
diff --git a/Sources/MathKeyboardSwift/Keyboards/KeyboardAction.swift b/Sources/MathKeyboardSwift/Keyboards/KeyboardAction.swift
new file mode 100644
index 0000000..7172a21
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Keyboards/KeyboardAction.swift
@@ -0,0 +1,6 @@
+enum KeyboardAction {
+ case insertText(String)
+ case backspace
+ case dismiss
+ case toggleShift
+}
diff --git a/Sources/MathKeyboardSwift/Keyboards/KeyboardCell.swift b/Sources/MathKeyboardSwift/Keyboards/KeyboardCell.swift
new file mode 100644
index 0000000..62bf64d
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Keyboards/KeyboardCell.swift
@@ -0,0 +1,106 @@
+//
+// KeyboardCell.swift
+// MathKeyboardSwift
+//
+// Created by Madiyar Aitbayev on 23/03/2026.
+//
+
+import Foundation
+import SwiftUI
+
+struct KeyboardCell: Identifiable {
+ enum Content {
+ case text(TextContent)
+ case image(ImageContent)
+ }
+
+ struct TextContent {
+ let value: String
+ let fontName: String
+ let fontSize: Double
+ let tone: TextTone
+ }
+
+ struct ImageContent {
+ let name: String
+ }
+
+ enum TextTone {
+ case light
+ case dark
+ case disabled
+ }
+
+ let id = UUID()
+ let content: Content
+ let action: () -> Void
+ let enabled: Bool
+ let accessibilityLabel: String
+ let pressedAsset: String?
+ let overlayAsset: String?
+ var padding: EdgeInsets
+
+ static func text(
+ label: String,
+ tone: TextTone,
+ fontName: String = "HelveticaNeue-Thin",
+ fontSize: CGFloat = 20,
+ action: @escaping () -> Void,
+ enabled: Bool,
+ accessibilityLabel: String? = nil,
+ pressedAsset: String? = nil,
+ overlayAsset: String? = nil,
+ padding: EdgeInsets = .zero,
+ ) -> KeyboardCell {
+ KeyboardCell(
+ content: .text(
+ TextContent(
+ value: label,
+ fontName: fontName,
+ fontSize: fontSize,
+ tone: tone
+ )
+ ),
+ action: action,
+ enabled: enabled,
+ accessibilityLabel: accessibilityLabel ?? label,
+ pressedAsset: pressedAsset,
+ overlayAsset: overlayAsset,
+ padding: padding
+ )
+ }
+
+ static func image(
+ imageName: String,
+ action: @escaping () -> Void,
+ enabled: Bool,
+ accessibilityLabel: String,
+ pressedAsset: String? = nil,
+ overlayAsset: String? = nil,
+ padding: EdgeInsets = .zero,
+ ) -> KeyboardCell {
+ KeyboardCell(
+ content: .image(ImageContent(name: imageName)),
+ action: action,
+ enabled: enabled,
+ accessibilityLabel: accessibilityLabel,
+ pressedAsset: pressedAsset,
+ overlayAsset: overlayAsset,
+ padding: padding
+ )
+ }
+}
+
+extension EdgeInsets {
+ static var zero: EdgeInsets {
+ EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
+ }
+
+ static func bottom(_ length: CGFloat) -> EdgeInsets {
+ EdgeInsets(top: 0, leading: 0, bottom: length, trailing: 0)
+ }
+
+ static func top(_ length: CGFloat) -> EdgeInsets {
+ EdgeInsets(top: length, leading: 0, bottom: 0, trailing: 0)
+ }
+}
diff --git a/Sources/MathKeyboardSwift/Keyboards/KeyboardFontRegistry.swift b/Sources/MathKeyboardSwift/Keyboards/KeyboardFontRegistry.swift
new file mode 100644
index 0000000..6febe19
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Keyboards/KeyboardFontRegistry.swift
@@ -0,0 +1,25 @@
+//
+// MTMathKeyboard.swift
+// MathKeyboardSwift
+//
+// Created by Madiyar Aitbayev on 23/03/2026.
+//
+
+import SwiftUI
+
+enum KeyboardFontRegistry {
+ static let variableFontName: String = {
+ guard
+ let fontURL = Bundle.module.url(forResource: "lmroman10-bolditalic", withExtension: "otf"),
+ let provider = CGDataProvider(url: fontURL as CFURL),
+ let font = CGFont(provider)
+ else {
+ return "HelveticaNeue"
+ }
+
+ let postScriptName = font.postScriptName as String? ?? "HelveticaNeue"
+ var error: Unmanaged?
+ CTFontManagerRegisterGraphicsFont(font, &error)
+ return postScriptName
+ }()
+}
diff --git a/Sources/MathKeyboardSwift/Keyboards/LettersKeyboardView.swift b/Sources/MathKeyboardSwift/Keyboards/LettersKeyboardView.swift
new file mode 100644
index 0000000..e5ddaa2
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Keyboards/LettersKeyboardView.swift
@@ -0,0 +1,189 @@
+import Foundation
+import SwiftUI
+
+struct LettersKeyboardView: View {
+ let state: KeyboardState
+ let isLowercase: Bool
+ let onShift: () -> Void
+ let onAction: (KeyboardAction) -> Void
+
+ private var topRow: [String] {
+ makeLetterRow(["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"])
+ }
+
+ private var middleRow: [String] {
+ makeLetterRow(["a", "s", "d", "f", "g", "h", "j", "k", "l"])
+ }
+
+ private var bottomRow: [String] {
+ makeLetterRow(["z", "x", "c", "v", "b", "n", "m"])
+ }
+
+ private var greekRow: [GreekKey] {
+ if isLowercase {
+ return [
+ GreekKey(label: "α", accessibilityLabel: "alpha"),
+ GreekKey(label: "Δ", accessibilityLabel: "capital delta"),
+ GreekKey(label: "σ", accessibilityLabel: "sigma"),
+ GreekKey(label: "μ", accessibilityLabel: "mu"),
+ GreekKey(label: "λ", accessibilityLabel: "lambda"),
+ ]
+ }
+
+ return [
+ GreekKey(label: "ρ", accessibilityLabel: "rho"),
+ GreekKey(label: "ω", accessibilityLabel: "omega"),
+ GreekKey(label: "Φ", accessibilityLabel: "capital phi"),
+ GreekKey(label: "ν", accessibilityLabel: "nu"),
+ GreekKey(label: "β", accessibilityLabel: "beta"),
+ ]
+ }
+
+ var body: some View {
+ GeometryReader { proxy in
+ let unitWidth = proxy.size.width / 10
+ let rowHeight = proxy.size.height / 4
+
+ ZStack {
+ Image("Letters Keyboard", bundle: .module)
+ .resizable()
+ .frame(width: proxy.size.width, height: proxy.size.height)
+
+ VStack(spacing: 0) {
+ letterRow(topRow, horizontalInset: 0, unitWidth: unitWidth, rowHeight: rowHeight)
+ letterRow(
+ middleRow, horizontalInset: unitWidth / 2, unitWidth: unitWidth, rowHeight: rowHeight)
+ bottomLetterRow(unitWidth: unitWidth, rowHeight: rowHeight)
+ greekRowView(unitWidth: unitWidth, rowHeight: rowHeight)
+ }
+ }
+ }
+ }
+
+ @ViewBuilder
+ private func letterRow(
+ _ letters: [String],
+ horizontalInset: CGFloat,
+ unitWidth: CGFloat,
+ rowHeight: CGFloat
+ ) -> some View {
+ HStack(spacing: 0) {
+ Color.clear.frame(width: horizontalInset)
+ ForEach(letters, id: \.self) { letter in
+ letterCell(letter)
+ .frame(width: unitWidth, height: rowHeight)
+ }
+ Color.clear.frame(width: horizontalInset)
+ }
+ }
+
+ @ViewBuilder
+ private func bottomLetterRow(unitWidth: CGFloat, rowHeight: CGFloat) -> some View {
+ HStack(spacing: 0) {
+ KeyButton(shiftCell)
+ .frame(width: unitWidth * 1.5, height: rowHeight)
+
+ ForEach(bottomRow, id: \.self) { letter in
+ letterCell(letter)
+ .frame(width: unitWidth, height: rowHeight)
+ }
+
+ KeyButton(backspaceCell)
+ .frame(width: unitWidth * 1.5, height: rowHeight)
+ }
+ }
+
+ @ViewBuilder
+ private func greekRowView(unitWidth: CGFloat, rowHeight: CGFloat) -> some View {
+ HStack(spacing: 0) {
+ KeyButton(dismissCell)
+ .frame(width: unitWidth * 2.5, height: rowHeight)
+
+ ForEach(greekRow) { key in
+ KeyButton(greekCell(key))
+ .frame(width: unitWidth, height: rowHeight)
+ }
+
+ KeyButton(enterCell)
+ .frame(width: unitWidth * 2.5, height: rowHeight)
+ }
+ }
+
+ private func makeLetterRow(_ letters: [String]) -> [String] {
+ if isLowercase {
+ return letters
+ }
+ return letters.map { $0.uppercased() }
+ }
+
+ private func letterCell(_ label: String) -> KeyButton {
+ KeyButton(
+ .text(
+ label: label,
+ tone: .dark,
+ action: { onAction(.insertText(label)) },
+ enabled: true,
+ pressedAsset: "Keyboard-azure-pressed"
+ )
+ )
+ }
+
+ private func greekCell(_ key: GreekKey) -> KeyboardCell {
+ .text(
+ label: key.label,
+ tone: .dark,
+ fontName: "CourierNewPS-ItalicMT",
+ action: { onAction(.insertText(key.label)) },
+ enabled: true,
+ accessibilityLabel: key.accessibilityLabel,
+ pressedAsset: "Keyboard-azure-pressed"
+ )
+ }
+
+ private var shiftCell: KeyboardCell {
+ .image(
+ imageName: "Shift",
+ action: onShift,
+ enabled: true,
+ accessibilityLabel: "Shift",
+ pressedAsset: "Keyboard-grey-pressed"
+ )
+ }
+
+ private var backspaceCell: KeyboardCell {
+ .image(
+ imageName: "Backspace Small",
+ action: { onAction(.backspace) },
+ enabled: true,
+ accessibilityLabel: "Backspace",
+ pressedAsset: "Keyboard-grey-pressed"
+ )
+ }
+
+ private var dismissCell: KeyboardCell {
+ .image(
+ imageName: "Keyboard Down",
+ action: { onAction(.dismiss) },
+ enabled: true,
+ accessibilityLabel: "Dismiss keyboard",
+ pressedAsset: "Keyboard-grey-pressed"
+ )
+ }
+
+ private var enterCell: KeyboardCell {
+ .text(
+ label: "Enter",
+ tone: .light,
+ fontName: "HelveticaNeue-Light",
+ action: { onAction(.insertText("\n")) },
+ enabled: true,
+ pressedAsset: "Keyboard-grey-pressed"
+ )
+ }
+}
+
+private struct GreekKey: Identifiable {
+ let id = UUID()
+ let label: String
+ let accessibilityLabel: String
+}
diff --git a/Sources/MathKeyboardSwift/Keyboards/MainKeyboardView.swift b/Sources/MathKeyboardSwift/Keyboards/MainKeyboardView.swift
new file mode 100644
index 0000000..b1c2430
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Keyboards/MainKeyboardView.swift
@@ -0,0 +1,101 @@
+import Foundation
+import MathEditorSwift
+import SwiftUI
+import iosMath
+
+struct MainKeyboardView: View {
+ let backgroundImageName: String
+ let middleColumns: [[KeyboardCell]]
+ let state: KeyboardState
+ let onAction: (KeyboardAction) -> Void
+
+ var body: some View {
+ GeometryReader { proxy in
+ let totalWidth = proxy.size.width
+ let totalHeight = proxy.size.height
+ let utilityWidth = totalWidth * 0.225
+ let standardColumnWidth = (totalWidth - utilityWidth) / 5
+ let rowHeight = totalHeight / 4
+
+ ZStack {
+ Image(backgroundImageName, bundle: .module)
+ .resizable()
+ .frame(width: totalWidth, height: totalHeight)
+
+ HStack(spacing: 0) {
+ VStack(spacing: 0) {
+ ForEach(featuresColumn) { item in
+ KeyButton(item).frame(width: standardColumnWidth, height: rowHeight)
+ }
+ }
+
+ Grid(horizontalSpacing: 0, verticalSpacing: 0) {
+ ForEach(0..<4, id: \.self) { row in
+ GridRow {
+ ForEach(0..<4, id: \.self) { column in
+ KeyButton(middleColumns[column][row])
+ .frame(width: standardColumnWidth, height: rowHeight)
+ }
+ }
+ }
+ }
+
+ VStack(spacing: 0) {
+ KeyButton(backspaceCell).frame(width: utilityWidth, height: rowHeight)
+ KeyButton(enterCell).frame(width: utilityWidth, height: rowHeight * 2)
+ KeyButton(dismissCell).frame(width: utilityWidth, height: rowHeight)
+ }
+ }
+ }
+ .frame(width: totalWidth, height: totalHeight)
+ }
+ }
+}
+
+extension MainKeyboardView {
+ private var featuresColumn: [KeyboardCell] {
+ [
+ .text(
+ label: "x", tone: .light, fontName: KeyboardFontRegistry.variableFontName,
+ action: { onAction(.insertText("x")) }, enabled: state.variablesAllowed,
+ pressedAsset: "Keyboard-marine-pressed", padding: .bottom(10)),
+ .text(
+ label: "y", tone: .light, fontName: KeyboardFontRegistry.variableFontName,
+ action: { onAction(.insertText("y")) }, enabled: state.variablesAllowed,
+ pressedAsset: "Keyboard-marine-pressed", padding: .bottom(10)),
+ .image(
+ imageName: "Fraction",
+ action: { onAction(.insertText(MTSymbolFractionSlash)) },
+ enabled: state.fractionsAllowed,
+ accessibilityLabel: "Fraction",
+ pressedAsset: "Keyboard-marine-pressed"),
+ .image(
+ imageName: "Exponent",
+ action: { onAction(.insertText("^")) }, enabled: true, accessibilityLabel: "Exponent",
+ pressedAsset: "Keyboard-marine-pressed",
+ overlayAsset: state.exponentHighlighted ? "blue-button-highlighted" : nil),
+ ]
+ }
+ private var backspaceCell: KeyboardCell {
+ KeyboardCell.image(
+ imageName: "Backspace",
+ action: { onAction(.backspace) }, enabled: true, accessibilityLabel: "Backspace",
+ pressedAsset: "Keyboard-grey-pressed")
+ }
+ private var enterCell: KeyboardCell {
+ KeyboardCell.text(
+ label: "Enter", tone: .light,
+ fontName: "Helvetica Neue Light",
+ action: { onAction(.insertText("\n")) }, enabled: true,
+ pressedAsset: "Keyboard-grey-pressed")
+ }
+ private var dismissCell: KeyboardCell {
+ KeyboardCell.image(
+ imageName: "Keyboard Down",
+ action: { onAction(.dismiss) }, enabled: true, accessibilityLabel: "Dismiss keyboard",
+ pressedAsset: "Keyboard-grey-pressed",
+ padding: .bottom(5)
+ )
+ }
+
+}
diff --git a/Sources/MathKeyboardSwift/Keyboards/NumbersKeyboardView.swift b/Sources/MathKeyboardSwift/Keyboards/NumbersKeyboardView.swift
new file mode 100644
index 0000000..5f9bbaf
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Keyboards/NumbersKeyboardView.swift
@@ -0,0 +1,89 @@
+import Foundation
+import SwiftUI
+
+struct NumbersKeyboardView: View {
+ let state: KeyboardState
+ let onAction: (KeyboardAction) -> Void
+
+ var body: some View {
+ MainKeyboardView(
+ backgroundImageName: "Numbers Keyboard",
+ middleColumns: makeNumbersGrid(state: state, onAction: onAction),
+ state: state,
+ onAction: onAction
+ )
+ }
+}
+
+private func makeNumbersGrid(
+ state: KeyboardState,
+ onAction: @escaping (KeyboardAction) -> Void
+) -> [[KeyboardCell]] {
+ let numbersLeftItems: [KeyboardCell] = [
+ .text(
+ label: "7", tone: .dark, action: { onAction(.insertText("7")) },
+ enabled: state.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"),
+ .text(
+ label: "4", tone: .dark, action: { onAction(.insertText("4")) },
+ enabled: state.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"),
+ .text(
+ label: "1", tone: .dark, action: { onAction(.insertText("1")) },
+ enabled: state.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"),
+ .text(
+ label: "0", tone: .dark, action: { onAction(.insertText("0")) },
+ enabled: state.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"),
+ ]
+ let numbersMiddleItems: [KeyboardCell] = [
+ .text(
+ label: "8", tone: .dark, action: { onAction(.insertText("8")) },
+ enabled: state.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"),
+ .text(
+ label: "5", tone: .dark, action: { onAction(.insertText("5")) },
+ enabled: state.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"),
+ .text(
+ label: "2", tone: .dark, action: { onAction(.insertText("2")) },
+ enabled: state.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"),
+ .text(
+ label: ".", tone: .dark, action: { onAction(.insertText(".")) },
+ enabled: state.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"),
+ ]
+ let numbersRightItems: [KeyboardCell] = [
+ .text(
+ label: "9", tone: .dark, action: { onAction(.insertText("9")) },
+ enabled: state.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"),
+ .text(
+ label: "6", tone: .dark, action: { onAction(.insertText("6")) },
+ enabled: state.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"),
+ .text(
+ label: "3", tone: .dark, action: { onAction(.insertText("3")) },
+ enabled: state.numbersAllowed, pressedAsset: "Keyboard-grey-pressed"),
+ .text(
+ label: "=", tone: state.equalsAllowed ? .dark : .disabled,
+ action: { onAction(.insertText("=")) }, enabled: state.equalsAllowed,
+ pressedAsset: "Keyboard-grey-pressed",
+ overlayAsset: state.equalsAllowed ? nil : "num-button-disabled"),
+ ]
+ let operatorItems: [KeyboardCell] = [
+ .text(
+ label: "÷", tone: .dark, action: { onAction(.insertText("÷")) },
+ enabled: state.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed",
+ padding: .bottom(5)
+ ),
+ .text(
+ label: "×", tone: .dark, action: { onAction(.insertText("×")) },
+ enabled: state.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed",
+ padding: .bottom(7)
+ ),
+ .text(
+ label: "-", tone: .dark, fontSize: 25, action: { onAction(.insertText("-")) },
+ enabled: state.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed",
+ padding: .bottom(9)
+ ),
+ .text(
+ label: "+", tone: .dark, action: { onAction(.insertText("+")) },
+ enabled: state.operatorsAllowed, pressedAsset: "Keyboard-orange-pressed",
+ padding: .bottom(5)
+ ),
+ ]
+ return [numbersLeftItems, numbersMiddleItems, numbersRightItems, operatorItems]
+}
diff --git a/Sources/MathKeyboardSwift/Keyboards/OperationsKeyboardView.swift b/Sources/MathKeyboardSwift/Keyboards/OperationsKeyboardView.swift
new file mode 100644
index 0000000..5aa3c1f
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Keyboards/OperationsKeyboardView.swift
@@ -0,0 +1,81 @@
+import Foundation
+import SwiftUI
+
+struct OperationsKeyboardView: View {
+ let state: KeyboardState
+ let onAction: (KeyboardAction) -> Void
+
+ var body: some View {
+ MainKeyboardView(
+ backgroundImageName: "Operations Keyboard",
+ middleColumns: makeOperationsGrid(state: state, onAction: onAction),
+ state: state,
+ onAction: onAction
+ )
+ }
+}
+
+private func makeOperationsGrid(
+ state: KeyboardState,
+ onAction: @escaping (KeyboardAction) -> Void
+) -> [[KeyboardCell]] {
+ let groupingLeftItems: [KeyboardCell] = [
+ .text(
+ label: "(", tone: .dark, action: { onAction(.insertText("(")) }, enabled: true,
+ pressedAsset: "Keyboard-orange-pressed"),
+ .text(
+ label: "[", tone: .dark, action: { onAction(.insertText("[")) }, enabled: true,
+ pressedAsset: "Keyboard-orange-pressed"),
+ .text(
+ label: "{", tone: .dark, action: { onAction(.insertText("{")) }, enabled: true,
+ pressedAsset: "Keyboard-orange-pressed"),
+ .text(
+ label: "!", tone: .dark, action: { onAction(.insertText("!")) }, enabled: true,
+ pressedAsset: "Keyboard-orange-pressed"),
+ ]
+ let groupingRightItems: [KeyboardCell] = [
+ .text(
+ label: ")", tone: .dark, action: { onAction(.insertText(")")) }, enabled: true,
+ pressedAsset: "Keyboard-orange-pressed"),
+ .text(
+ label: "]", tone: .dark, action: { onAction(.insertText("]")) }, enabled: true,
+ pressedAsset: "Keyboard-orange-pressed"),
+ .text(
+ label: "}", tone: .dark, action: { onAction(.insertText("}")) }, enabled: true,
+ pressedAsset: "Keyboard-orange-pressed"),
+ .text(
+ label: "∞", tone: .dark, action: { onAction(.insertText("∞")) }, enabled: true,
+ pressedAsset: "Keyboard-orange-pressed"),
+ ]
+ let relationItems: [KeyboardCell] = [
+ .text(
+ label: "<", tone: .dark, action: { onAction(.insertText("<")) }, enabled: true,
+ pressedAsset: "Keyboard-orange-pressed"),
+ .text(
+ label: "≤", tone: .dark, action: { onAction(.insertText("≤")) }, enabled: true,
+ pressedAsset: "Keyboard-orange-pressed"),
+ .text(
+ label: "|□|", tone: .dark, fontSize: 24, action: { onAction(.insertText("||")) },
+ enabled: true, accessibilityLabel: "Absolute value",
+ pressedAsset: "Keyboard-orange-pressed"
+ ),
+ .text(
+ label: ":", tone: .dark, action: { onAction(.insertText(":")) }, enabled: true,
+ pressedAsset: "Keyboard-orange-pressed"),
+ ]
+ let punctuationItems: [KeyboardCell] = [
+ .text(
+ label: ">", tone: .dark, action: { onAction(.insertText(">")) }, enabled: true,
+ pressedAsset: "Keyboard-orange-pressed"),
+ .text(
+ label: "≥", tone: .dark, action: { onAction(.insertText("≥")) }, enabled: true,
+ pressedAsset: "Keyboard-orange-pressed"),
+ .text(
+ label: "%", tone: .dark, action: { onAction(.insertText("%")) }, enabled: true,
+ pressedAsset: "Keyboard-orange-pressed"),
+ .text(
+ label: ",", tone: .dark, action: { onAction(.insertText(",")) }, enabled: true,
+ pressedAsset: "Keyboard-orange-pressed"),
+ ]
+ return [groupingLeftItems, groupingRightItems, relationItems, punctuationItems]
+}
diff --git a/Sources/MathKeyboardSwift/MTMathKeyboardSwiftRootView.swift b/Sources/MathKeyboardSwift/MTMathKeyboardSwiftRootView.swift
new file mode 100644
index 0000000..0cf3be2
--- /dev/null
+++ b/Sources/MathKeyboardSwift/MTMathKeyboardSwiftRootView.swift
@@ -0,0 +1,145 @@
+#if os(iOS)
+
+ import MathEditorSwift
+ import SwiftUI
+ import UIKit
+
+ public final class MTMathKeyboardSwiftRootView: MTView, MTMathKeyboard,
+ UIInputViewAudioFeedback
+ {
+ private static let defaultTab: KeyboardTab = .numbers
+ private static let shared = MTMathKeyboardSwiftRootView()
+
+ private var state = KeyboardState()
+ private weak var textInput: (any MTView & MTKeyInput)?
+ private lazy var hostingController = UIHostingController(
+ rootView: makeRootView()
+ )
+
+ public var enableInputClicksWhenVisible: Bool {
+ true
+ }
+
+ public override init(frame: CGRect) {
+ super.init(frame: frame)
+ commonInit()
+ }
+
+ public required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ commonInit()
+ }
+
+ public static func sharedInstance() -> MTMathKeyboardSwiftRootView {
+ shared
+ }
+
+ public func switchToDefaultTab() {
+ updateState { $0.currentTab = Self.defaultTab }
+ }
+
+ public var equalsAllowed: Bool {
+ get { state.equalsAllowed }
+ set { updateState { $0.equalsAllowed = newValue } }
+ }
+
+ public var fractionsAllowed: Bool {
+ get { state.fractionsAllowed }
+ set { updateState { $0.fractionsAllowed = newValue } }
+ }
+
+ public var variablesAllowed: Bool {
+ get { state.variablesAllowed }
+ set { updateState { $0.variablesAllowed = newValue } }
+ }
+
+ public var numbersAllowed: Bool {
+ get { state.numbersAllowed }
+ set { updateState { $0.numbersAllowed = newValue } }
+ }
+
+ public var operatorsAllowed: Bool {
+ get { state.operatorsAllowed }
+ set { updateState { $0.operatorsAllowed = newValue } }
+ }
+
+ public var exponentHighlighted: Bool {
+ get { state.exponentHighlighted }
+ set { updateState { $0.exponentHighlighted = newValue } }
+ }
+
+ public var squareRootHighlighted: Bool {
+ get { state.squareRootHighlighted }
+ set { updateState { $0.squareRootHighlighted = newValue } }
+ }
+
+ public var radicalHighlighted: Bool {
+ get { state.radicalHighlighted }
+ set { updateState { $0.radicalHighlighted = newValue } }
+ }
+
+ public func startedEditing(_ label: any MTView & MTKeyInput) {
+ textInput = label
+ updateRootView()
+ }
+
+ public func finishedEditing(_ label: any MTView & MTKeyInput) {
+ if textInput === label {
+ textInput = nil
+ updateRootView()
+ }
+ }
+
+ private func updateState(_ update: (inout KeyboardState) -> Void) {
+ update(&state)
+ updateRootView()
+ }
+
+ private func makeRootView() -> MathKeyboardRootView {
+ MathKeyboardRootView(
+ state: state,
+ onTabSelected: { [weak self] tab in
+ self?.updateState { $0.currentTab = tab }
+ },
+ onAction: { [weak self] action in
+ self?.handleKeyboardAction(action)
+ }
+ )
+ }
+
+ private func commonInit() {
+ backgroundColor = .white
+ autoresizingMask = [.flexibleWidth, .flexibleHeight]
+
+ let hostedView = hostingController.view!
+ if #available(iOS 16.4, *) {
+ hostingController.safeAreaRegions = []
+ }
+ hostedView.backgroundColor = .clear
+ addSubview(hostedView)
+ hostedView.pinToSuperview()
+
+ updateRootView()
+ }
+
+ private func updateRootView() {
+ hostingController.rootView = makeRootView()
+ }
+
+ private func handleKeyboardAction(_ action: KeyboardAction) {
+ UIDevice.current.playInputClick()
+
+ switch action {
+ case .insertText(let text):
+ textInput?.insertText(text)
+ case .backspace:
+ textInput?.deleteBackward()
+ case .dismiss:
+ textInput?.resignFirstResponder()
+ case .toggleShift:
+ updateState { $0.isLowercase.toggle() }
+ }
+ }
+ }
+
+#endif
diff --git a/Sources/MathKeyboardSwift/MathKeyboardRootView.swift b/Sources/MathKeyboardSwift/MathKeyboardRootView.swift
new file mode 100644
index 0000000..f8f717f
--- /dev/null
+++ b/Sources/MathKeyboardSwift/MathKeyboardRootView.swift
@@ -0,0 +1,57 @@
+//
+// MathKeyboardRootView.swift
+// MathEditor
+//
+// Created by Madiyar Aitbayev on 22/03/2026.
+//
+
+import SwiftUI
+
+public struct MathKeyboardRootView: View {
+ let state: KeyboardState
+ let onTabSelected: (KeyboardTab) -> Void
+ let onAction: (KeyboardAction) -> Void
+
+ public var body: some View {
+ GeometryReader { proxy in
+ let totalHeight = proxy.size.height
+ let tabHeight = totalHeight / 5.0
+ let keyboardHeight = totalHeight - tabHeight
+
+ VStack(spacing: 0) {
+ HStack(spacing: 0) {
+ ForEach(KeyboardTab.allCases) { tab in
+ Button {
+ onTabSelected(tab)
+ } label: {
+ Image(tab.imageName(for: state), bundle: .module)
+ .renderingMode(.original)
+ .resizable()
+ .scaledToFit()
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+ .padding(.horizontal, 8)
+ .padding(.vertical, 6)
+ }
+ .buttonStyle(.plain)
+ .background(Color(white: 0.768627451))
+ }
+ }
+ .frame(height: tabHeight)
+
+ KeyboardContainerView(
+ state: state,
+ onAction: onAction
+ )
+ .frame(height: keyboardHeight)
+ }
+ .background(Color.white)
+ .ignoresSafeArea()
+ }
+ }
+}
+
+extension KeyboardTab {
+ fileprivate func imageName(for state: KeyboardState) -> String {
+ state.currentTab == self ? imageNames.selected : imageNames.normal
+ }
+}
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/Contents.json
new file mode 100644
index 0000000..067e4c8
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x",
+ "filename" : "left arrow disabled 1x.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x",
+ "filename" : "left arrow disabled 2x.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x",
+ "filename" : "left arrow disabled 3x.png"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 1x.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 1x.png
new file mode 100644
index 0000000..d24a0ee
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 1x.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 2x.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 2x.png
new file mode 100644
index 0000000..33a4d0c
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 2x.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 3x.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 3x.png
new file mode 100644
index 0000000..fd77cfe
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/back-arrow-disabled.imageset/left arrow disabled 3x.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/back-arrow.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/back-arrow.imageset/Contents.json
new file mode 100644
index 0000000..31fc527
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/back-arrow.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x",
+ "filename" : "left arrow 1x.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x",
+ "filename" : "left arrow 2x.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x",
+ "filename" : "left arrow 3x.png"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 1x.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 1x.png
new file mode 100644
index 0000000..5784e5f
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 1x.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 2x.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 2x.png
new file mode 100644
index 0000000..b77d261
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 2x.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 3x.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 3x.png
new file mode 100644
index 0000000..aeaa03d
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/back-arrow.imageset/left arrow 3x.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/blue-button-highlighted.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/blue-button-highlighted.imageset/Contents.json
new file mode 100644
index 0000000..56db975
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/blue-button-highlighted.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x",
+ "filename" : "blue-pressed.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/blue-button-highlighted.imageset/blue-pressed.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/blue-button-highlighted.imageset/blue-pressed.png
new file mode 100644
index 0000000..57f709f
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/blue-button-highlighted.imageset/blue-pressed.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/Contents.json
new file mode 100644
index 0000000..a6edac4
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x",
+ "filename" : "right arrow disabled 1x.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x",
+ "filename" : "right arrow disabled 2x.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x",
+ "filename" : "right arrow disabled 3x.png"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 1x.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 1x.png
new file mode 100644
index 0000000..1d982c9
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 1x.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 2x.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 2x.png
new file mode 100644
index 0000000..62d40e9
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 2x.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 3x.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 3x.png
new file mode 100644
index 0000000..20f9466
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/front-arrow-disabled.imageset/right arrow disabled 3x.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/front-arrow.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/front-arrow.imageset/Contents.json
new file mode 100644
index 0000000..b8aabfb
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/front-arrow.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x",
+ "filename" : "right arrow 1x.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x",
+ "filename" : "right arrow 2x.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x",
+ "filename" : "right arrow 3x.png"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 1x.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 1x.png
new file mode 100644
index 0000000..feb11ea
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 1x.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 2x.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 2x.png
new file mode 100644
index 0000000..e4789ee
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 2x.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 3x.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 3x.png
new file mode 100644
index 0000000..c856f9b
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/front-arrow.imageset/right arrow 3x.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/grey-button-disabled.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/grey-button-disabled.imageset/Contents.json
new file mode 100644
index 0000000..808cdd4
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/grey-button-disabled.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x",
+ "filename" : "grey-button-disabled.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/grey-button-disabled.imageset/grey-button-disabled.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/grey-button-disabled.imageset/grey-button-disabled.png
new file mode 100644
index 0000000..c0c12ff
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/grey-button-disabled.imageset/grey-button-disabled.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/Contents.json
new file mode 100644
index 0000000..03197d3
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x",
+ "filename" : "keyboard-grey-pressed.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/keyboard-grey-pressed.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/keyboard-grey-pressed.png
new file mode 100644
index 0000000..353fbd4
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/grey-button-highlighted.imageset/keyboard-grey-pressed.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/ipad-background.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/ipad-background.imageset/Contents.json
new file mode 100644
index 0000000..9405a66
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/ipad-background.imageset/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x",
+ "filename" : "ipad-keyboard1x.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x",
+ "filename" : "ipad-keyboard2x.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard1x.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard1x.png
new file mode 100644
index 0000000..80689a1
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard1x.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard2x.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard2x.png
new file mode 100644
index 0000000..3cc9208
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/ipad-background.imageset/ipad-keyboard2x.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/iphone-background.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/iphone-background.imageset/Contents.json
new file mode 100644
index 0000000..75260ac
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/iphone-background.imageset/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x",
+ "filename" : "keyboard-background1x.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x",
+ "filename" : "keyboard-background2x.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background1x.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background1x.png
new file mode 100644
index 0000000..399996f
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background1x.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background2x.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background2x.png
new file mode 100644
index 0000000..3a411c2
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/iphone-background.imageset/keyboard-background2x.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/Contents.json
new file mode 100644
index 0000000..228a6a7
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x",
+ "filename" : "kb-dark-blue-pressed.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/kb-dark-blue-pressed.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/kb-dark-blue-pressed.png
new file mode 100644
index 0000000..37951ab
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/kb-dark-blue-pressed.imageset/kb-dark-blue-pressed.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/Contents.json
new file mode 100644
index 0000000..6b2910c
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x",
+ "filename" : "kb-slide-button-pressed.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/kb-slide-button-pressed.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/kb-slide-button-pressed.png
new file mode 100644
index 0000000..6978659
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/kbslide-button-highlighted.imageset/kb-slide-button-pressed.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/Contents.json
new file mode 100644
index 0000000..9ae1d29
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x",
+ "filename" : "keyboard-slide11x.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x",
+ "filename" : "keyboard-slide12x.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide11x.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide11x.png
new file mode 100644
index 0000000..4c4d106
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide11x.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide12x.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide12x.png
new file mode 100644
index 0000000..7459821
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg1.imageset/keyboard-slide12x.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/Contents.json
new file mode 100644
index 0000000..c9fa654
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x",
+ "filename" : "keyboard-slide21x.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x",
+ "filename" : "keyboard-slide22x.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide21x.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide21x.png
new file mode 100644
index 0000000..49a60d2
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide21x.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide22x.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide22x.png
new file mode 100644
index 0000000..5076081
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg2.imageset/keyboard-slide22x.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/Contents.json
new file mode 100644
index 0000000..5e86f7a
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x",
+ "filename" : "keyboard-slide31x.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x",
+ "filename" : "keyboard-slide32x.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide31x.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide31x.png
new file mode 100644
index 0000000..82b639c
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide31x.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide32x.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide32x.png
new file mode 100644
index 0000000..77d9cb5
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/keyboard-slide-bg3.imageset/keyboard-slide32x.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/Contents.json
new file mode 100644
index 0000000..14852e1
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x",
+ "filename" : "multiplication1x.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x",
+ "filename" : "multiplication2x-ipad.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication1x.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication1x.png
new file mode 100644
index 0000000..8c8876d
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication1x.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication2x-ipad.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication2x-ipad.png
new file mode 100644
index 0000000..2faa5bf
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/multiplication-icon-ipad.imageset/multiplication2x-ipad.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/Contents.json
new file mode 100644
index 0000000..ebe6ab3
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x",
+ "filename" : "multiplication1x-iphone.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x",
+ "filename" : "multiplication2x-iphone.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication1x-iphone.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication1x-iphone.png
new file mode 100644
index 0000000..12c5458
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication1x-iphone.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication2x-iphone.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication2x-iphone.png
new file mode 100644
index 0000000..e03c22f
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/multiplication-icon-iphone.imageset/multiplication2x-iphone.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/num-button-disabled.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/num-button-disabled.imageset/Contents.json
new file mode 100644
index 0000000..808cdd4
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/num-button-disabled.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x",
+ "filename" : "grey-button-disabled.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/num-button-disabled.imageset/grey-button-disabled.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/num-button-disabled.imageset/grey-button-disabled.png
new file mode 100644
index 0000000..b9ad3be
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/num-button-disabled.imageset/grey-button-disabled.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/num-button-highlighted.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/num-button-highlighted.imageset/Contents.json
new file mode 100644
index 0000000..611ff31
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/num-button-highlighted.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x",
+ "filename" : "keyboard-num-pressed.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/num-button-highlighted.imageset/keyboard-num-pressed.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/num-button-highlighted.imageset/keyboard-num-pressed.png
new file mode 100644
index 0000000..1bc84a4
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/num-button-highlighted.imageset/keyboard-num-pressed.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/Contents.json
new file mode 100644
index 0000000..9f29cb3
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x",
+ "filename" : "keyboard-orange-pressed.png"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/keyboard-orange-pressed.png b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/keyboard-orange-pressed.png
new file mode 100644
index 0000000..7021eec
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/KeyboardAssests.xcassets/orange-button-highlighted.imageset/keyboard-orange-pressed.png differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Backspace Small.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Backspace Small.pdf
new file mode 100644
index 0000000..40e6cd4
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Backspace Small.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Contents.json
new file mode 100644
index 0000000..f09a029
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Backspace Small.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Backspace Small.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Backspace.imageset/Backspace.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Backspace.imageset/Backspace.pdf
new file mode 100644
index 0000000..cfcd807
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Backspace.imageset/Backspace.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Backspace.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Backspace.imageset/Contents.json
new file mode 100644
index 0000000..28439e2
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Backspace.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Backspace.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Exponent.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Exponent.imageset/Contents.json
new file mode 100644
index 0000000..523c00a
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Exponent.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Exponent.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Exponent.imageset/Exponent.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Exponent.imageset/Exponent.pdf
new file mode 100644
index 0000000..dd5bfeb
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Exponent.imageset/Exponent.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Fraction.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Fraction.imageset/Contents.json
new file mode 100644
index 0000000..9c07ead
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Fraction.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Fraction.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Fraction.imageset/Fraction.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Fraction.imageset/Fraction.pdf
new file mode 100644
index 0000000..a5a3f96
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Fraction.imageset/Fraction.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Contents.json
new file mode 100644
index 0000000..c4cbd27
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Functions Keyboard.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Functions Keyboard.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Functions Keyboard.pdf
new file mode 100644
index 0000000..292ed6b
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Functions Keyboard.imageset/Functions Keyboard.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Contents.json
new file mode 100644
index 0000000..3fb6c37
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Functions Symbol.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Functions Symbol.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Functions Symbol.pdf
new file mode 100644
index 0000000..fd14e50
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Functions Symbol.imageset/Functions Symbol.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Contents.json
new file mode 100644
index 0000000..2335dad
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Keyboard Down.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Keyboard Down.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Keyboard Down.pdf
new file mode 100644
index 0000000..de758f5
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard Down.imageset/Keyboard Down.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Contents.json
new file mode 100644
index 0000000..186ba84
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Keyboard-azure-pressed.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Keyboard-azure-pressed.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Keyboard-azure-pressed.pdf
new file mode 100644
index 0000000..67007f0
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-azure-pressed.imageset/Keyboard-azure-pressed.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Contents.json
new file mode 100644
index 0000000..02c527f
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Keyboard-green-pressed.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Keyboard-green-pressed.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Keyboard-green-pressed.pdf
new file mode 100644
index 0000000..c7ee3b2
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-green-pressed.imageset/Keyboard-green-pressed.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Contents.json
new file mode 100644
index 0000000..02f937b
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Keyboard-grey-pressed.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Keyboard-grey-pressed.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Keyboard-grey-pressed.pdf
new file mode 100644
index 0000000..3328558
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-grey-pressed.imageset/Keyboard-grey-pressed.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Contents.json
new file mode 100644
index 0000000..058ab02
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Keyboard-marine-pressed.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Keyboard-marine-pressed.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Keyboard-marine-pressed.pdf
new file mode 100644
index 0000000..1dd756f
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-marine-pressed.imageset/Keyboard-marine-pressed.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Contents.json
new file mode 100644
index 0000000..567019b
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Keyboard-orange-pressed.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Keyboard-orange-pressed.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Keyboard-orange-pressed.pdf
new file mode 100644
index 0000000..6d3c6b3
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Keyboard-orange-pressed.imageset/Keyboard-orange-pressed.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Contents.json
new file mode 100644
index 0000000..9ad12a1
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Letter Symbol.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Letter Symbol.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Letter Symbol.pdf
new file mode 100644
index 0000000..37e03d3
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Letter Symbol.imageset/Letter Symbol.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Contents.json
new file mode 100644
index 0000000..53d8353
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Letters Keyboard.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Letters Keyboard.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Letters Keyboard.pdf
new file mode 100644
index 0000000..0bbcaea
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Letters Keyboard.imageset/Letters Keyboard.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Contents.json
new file mode 100644
index 0000000..64b7b1b
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Log Inverted.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Log Inverted.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Log Inverted.pdf
new file mode 100644
index 0000000..3a5c785
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Log Inverted.imageset/Log Inverted.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Log with base.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Log with base.imageset/Contents.json
new file mode 100644
index 0000000..084900d
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Log with base.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Log with base.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Log with base.imageset/Log with base.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Log with base.imageset/Log with base.pdf
new file mode 100644
index 0000000..2809297
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Log with base.imageset/Log with base.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Contents.json
new file mode 100644
index 0000000..3035c06
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Number Symbol.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Number Symbol.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Number Symbol.pdf
new file mode 100644
index 0000000..e16e728
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Number Symbol.imageset/Number Symbol.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Contents.json
new file mode 100644
index 0000000..4c1745d
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Numbers Keyboard.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Numbers Keyboard.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Numbers Keyboard.pdf
new file mode 100644
index 0000000..5223f19
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Numbers Keyboard.imageset/Numbers Keyboard.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Contents.json
new file mode 100644
index 0000000..def13fd
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Operations Keyboard.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Operations Keyboard.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Operations Keyboard.pdf
new file mode 100644
index 0000000..388605a
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Operations Keyboard.imageset/Operations Keyboard.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Contents.json
new file mode 100644
index 0000000..68827d4
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Operations Symbol.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Operations Symbol.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Operations Symbol.pdf
new file mode 100644
index 0000000..312ca49
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Operations Symbol.imageset/Operations Symbol.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Shift.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Shift.imageset/Contents.json
new file mode 100644
index 0000000..451c244
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Shift.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Shift.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Shift.imageset/Shift.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Shift.imageset/Shift.pdf
new file mode 100644
index 0000000..62f0226
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Shift.imageset/Shift.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Contents.json
new file mode 100644
index 0000000..4ebfa5a
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Sqrt White Fixed.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Sqrt White Fixed.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Sqrt White Fixed.pdf
new file mode 100644
index 0000000..f41533f
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Sqrt Inverted.imageset/Sqrt White Fixed.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Contents.json
new file mode 100644
index 0000000..8be156b
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Sqrt Power Inverted.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Sqrt Power Inverted.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Sqrt Power Inverted.pdf
new file mode 100644
index 0000000..fa6a74b
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Sqrt Power Inverted.imageset/Sqrt Power Inverted.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Contents.json
new file mode 100644
index 0000000..20a091b
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Sqrt with Power.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Sqrt with Power.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Sqrt with Power.pdf
new file mode 100644
index 0000000..d7c2166
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Sqrt with Power.imageset/Sqrt with Power.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Sqrt.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Sqrt.imageset/Contents.json
new file mode 100644
index 0000000..77856d4
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Sqrt.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Sqrt.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Sqrt.imageset/Sqrt.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Sqrt.imageset/Sqrt.pdf
new file mode 100644
index 0000000..32e996a
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Sqrt.imageset/Sqrt.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Contents.json
new file mode 100644
index 0000000..b9f4407
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Subscript Inverted.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Subscript Inverted.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Subscript Inverted.pdf
new file mode 100644
index 0000000..09e5f0d
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Subscript Inverted.imageset/Subscript Inverted.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Subscript.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Subscript.imageset/Contents.json
new file mode 100644
index 0000000..465f18e
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Subscript.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Subscript.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Subscript.imageset/Subscript.pdf b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Subscript.imageset/Subscript.pdf
new file mode 100644
index 0000000..37bbc58
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/NewKeyboardAssets.xcassets/Subscript.imageset/Subscript.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Contents.json
new file mode 100644
index 0000000..3fb6c37
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Functions Symbol.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Functions Symbol.pdf b/Sources/MathKeyboardSwift/Resources/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Functions Symbol.pdf
new file mode 100644
index 0000000..94dd9ef
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/WhiteBGKeyboardTab.xcassets/Functions Symbol wbg.imageset/Functions Symbol.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Contents.json
new file mode 100644
index 0000000..9ad12a1
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Letter Symbol.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Letter Symbol.pdf b/Sources/MathKeyboardSwift/Resources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Letter Symbol.pdf
new file mode 100644
index 0000000..d51fa45
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/WhiteBGKeyboardTab.xcassets/Letter Symbol wbg.imageset/Letter Symbol.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Contents.json
new file mode 100644
index 0000000..84ff917
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Numbers Symbol.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Numbers Symbol.pdf b/Sources/MathKeyboardSwift/Resources/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Numbers Symbol.pdf
new file mode 100644
index 0000000..a79d54e
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/WhiteBGKeyboardTab.xcassets/Numbers Symbol wbg.imageset/Numbers Symbol.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Contents.json b/Sources/MathKeyboardSwift/Resources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Contents.json
new file mode 100644
index 0000000..68827d4
--- /dev/null
+++ b/Sources/MathKeyboardSwift/Resources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Operations Symbol.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Sources/MathKeyboardSwift/Resources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Operations Symbol.pdf b/Sources/MathKeyboardSwift/Resources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Operations Symbol.pdf
new file mode 100644
index 0000000..6a2a12d
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/WhiteBGKeyboardTab.xcassets/Operations Symbol wbg.imageset/Operations Symbol.pdf differ
diff --git a/Sources/MathKeyboardSwift/Resources/lmroman10-bolditalic.otf b/Sources/MathKeyboardSwift/Resources/lmroman10-bolditalic.otf
new file mode 100644
index 0000000..98297be
Binary files /dev/null and b/Sources/MathKeyboardSwift/Resources/lmroman10-bolditalic.otf differ
diff --git a/Tests/MathEditorSwiftTests/Expectations.swift b/Tests/MathEditorSwiftTests/Expectations.swift
new file mode 100644
index 0000000..8d4b4e3
--- /dev/null
+++ b/Tests/MathEditorSwiftTests/Expectations.swift
@@ -0,0 +1,39 @@
+//
+// Expectations.swift
+// MathEditorSwift
+//
+// Created by Madiyar Aitbayev on 27/03/2026.
+//
+
+import Testing
+import iosMath
+
+/// Asserts that `MTMathListBuilder` serializes a `MTMathList` back to the expected LaTeX string.
+func expectLatex(
+ _ expected: String,
+ from mathList: MTMathList,
+ _ comment: Comment? = nil,
+ sourceLocation: SourceLocation = #_sourceLocation
+) {
+ let actual = MTMathListBuilder.mathList(toString: mathList)
+ #expect(
+ expected == actual,
+ comment ?? "LaTeX mismatch:\n expected: \"\(expected)\"\n actual: \"\(actual)\"",
+ sourceLocation: sourceLocation
+ )
+}
+
+/// Asserts that a `MTMathList.stringValue` equals the expected string.
+func expectStringValue(
+ of mathList: MTMathList,
+ to expected: String,
+ _ comment: Comment? = nil,
+ sourceLocation: SourceLocation = #_sourceLocation
+) {
+ let actual = mathList.stringValue
+ #expect(
+ expected == actual,
+ comment ?? "stringValue mismatch:\n expected: \"\(expected)\"\n actual: \"\(actual)\"",
+ sourceLocation: sourceLocation
+ )
+}
diff --git a/Tests/MathEditorSwiftTests/MTDisplayEditingTests.swift b/Tests/MathEditorSwiftTests/MTDisplayEditingTests.swift
new file mode 100644
index 0000000..c4b441a
--- /dev/null
+++ b/Tests/MathEditorSwiftTests/MTDisplayEditingTests.swift
@@ -0,0 +1,330 @@
+import CoreGraphics
+import Testing
+import iosMath
+
+@testable import MathEditorSwift
+
+private struct ClosestIndexCase {
+ let point: CGPoint
+ let expected: MTMathListIndex
+}
+
+@Suite(.serialized)
+struct MTDisplayEditingTests {
+ private let font: MTFont = MTFontManager().latinModernFont(withSize: 20)
+
+ private func assertClosestIndex(
+ expression: String,
+ cases: [ClosestIndexCase]
+ ) {
+ let mathList = MTMathListBuilder.build(from: expression)!
+ let displayList = MTTypesetter.createLine(for: mathList, font: font, style: .display)
+
+ for testCase in cases {
+ let actual = displayList.closestIndex(to: testCase.point)
+ #expect(
+ actual?.isEqual(testCase.expected) == true,
+ "Index \(String(describing: actual)) does not match \(testCase.expected) for point \(testCase.point)"
+ )
+ }
+ }
+
+ @Test("closest index for fraction")
+ func closestPointFraction() {
+ assertClosestIndex(expression: "\\frac{3}{2}", cases: fractionCases)
+ }
+
+ @Test("closest index for regular expression")
+ func closestPointRegular() {
+ assertClosestIndex(expression: "4+2", cases: regularCases)
+ }
+
+ @Test("closest index for regular plus fraction")
+ func closestPointRegularPlusFraction() {
+ assertClosestIndex(expression: "1+\\frac{3}{2}", cases: regularPlusFractionCases)
+ }
+
+ @Test("closest index for fraction plus regular")
+ func closestPointFractionPlusRegular() {
+ assertClosestIndex(expression: "\\frac{3}{2}+1", cases: fractionPlusRegularCases)
+ }
+
+ @Test("closest index for exponent")
+ func closestPointExponent() {
+ assertClosestIndex(expression: "2^3", cases: exponentCases)
+ }
+}
+
+private let fractionCases: [ClosestIndexCase] = [
+ .init(point: CGPoint(x: -10, y: 8), expected: .level0Index(0)),
+ .init(point: CGPoint(x: -10, y: 0), expected: .level0Index(0)),
+ .init(point: CGPoint(x: -10, y: 40), expected: .level0Index(0)),
+ .init(point: CGPoint(x: -10, y: -20), expected: .level0Index(0)),
+ .init(point: CGPoint(x: -2.5, y: 8), expected: .level0Index(0)),
+ .init(point: CGPoint(x: -2.5, y: 0), expected: .level0Index(0)),
+ .init(point: CGPoint(x: -2.5, y: 40), expected: .level0Index(0)),
+ .init(point: CGPoint(x: -2.5, y: -20), expected: .level0Index(0)),
+ .init(
+ point: CGPoint(x: -1, y: 0),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)),
+ .init(
+ point: CGPoint(x: -1, y: 8),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)),
+ .init(
+ point: CGPoint(x: -1, y: 40),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)),
+ .init(
+ point: CGPoint(x: -1, y: -20),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)),
+ .init(
+ point: CGPoint(x: 3, y: 0),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)),
+ .init(
+ point: CGPoint(x: 3, y: 8),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)),
+ .init(
+ point: CGPoint(x: 3, y: 40),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)),
+ .init(
+ point: CGPoint(x: 3, y: -20),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)),
+ .init(
+ point: CGPoint(x: 7, y: 0),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)),
+ .init(
+ point: CGPoint(x: 7, y: 8),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)),
+ .init(
+ point: CGPoint(x: 7, y: 40),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)),
+ .init(
+ point: CGPoint(x: 7, y: -20),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)),
+ .init(
+ point: CGPoint(x: 11, y: 0),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)),
+ .init(
+ point: CGPoint(x: 11, y: 8),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)),
+ .init(
+ point: CGPoint(x: 11, y: 40),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)),
+ .init(point: CGPoint(x: 11, y: -20), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 12.5, y: 8), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 12.5, y: 0), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 12.5, y: 40), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 12.5, y: -20), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 20, y: 8), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 20, y: 0), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 20, y: 40), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 20, y: -20), expected: .level0Index(1)),
+]
+
+private let regularCases: [ClosestIndexCase] = [
+ .init(point: CGPoint(x: -10, y: 8), expected: .level0Index(0)),
+ .init(point: CGPoint(x: -10, y: 0), expected: .level0Index(0)),
+ .init(point: CGPoint(x: -10, y: 40), expected: .level0Index(0)),
+ .init(point: CGPoint(x: -10, y: -20), expected: .level0Index(0)),
+ .init(point: CGPoint(x: 0, y: 0), expected: .level0Index(0)),
+ .init(point: CGPoint(x: 0, y: 8), expected: .level0Index(0)),
+ .init(point: CGPoint(x: 0, y: 40), expected: .level0Index(0)),
+ .init(point: CGPoint(x: 0, y: -20), expected: .level0Index(0)),
+ .init(point: CGPoint(x: 10, y: 0), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 10, y: 8), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 10, y: 40), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 10, y: -20), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 15, y: 0), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 15, y: 8), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 15, y: 40), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 15, y: -20), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 25, y: 0), expected: .level0Index(2)),
+ .init(point: CGPoint(x: 25, y: 8), expected: .level0Index(2)),
+ .init(point: CGPoint(x: 25, y: 40), expected: .level0Index(2)),
+ .init(point: CGPoint(x: 25, y: -20), expected: .level0Index(2)),
+ .init(point: CGPoint(x: 35, y: 0), expected: .level0Index(2)),
+ .init(point: CGPoint(x: 35, y: 8), expected: .level0Index(2)),
+ .init(point: CGPoint(x: 35, y: 40), expected: .level0Index(2)),
+ .init(point: CGPoint(x: 35, y: -20), expected: .level0Index(2)),
+ .init(point: CGPoint(x: 45, y: 0), expected: .level0Index(3)),
+ .init(point: CGPoint(x: 45, y: 8), expected: .level0Index(3)),
+ .init(point: CGPoint(x: 45, y: 40), expected: .level0Index(3)),
+ .init(point: CGPoint(x: 45, y: -20), expected: .level0Index(3)),
+ .init(point: CGPoint(x: 55, y: 0), expected: .level0Index(3)),
+ .init(point: CGPoint(x: 55, y: 8), expected: .level0Index(3)),
+ .init(point: CGPoint(x: 55, y: 40), expected: .level0Index(3)),
+ .init(point: CGPoint(x: 55, y: -20), expected: .level0Index(3)),
+]
+
+private let regularPlusFractionCases: [ClosestIndexCase] = [
+ .init(point: CGPoint(x: 30, y: 0), expected: .level0Index(2)),
+ .init(point: CGPoint(x: 30, y: 8), expected: .level0Index(2)),
+ .init(point: CGPoint(x: 30, y: 40), expected: .level0Index(2)),
+ .init(point: CGPoint(x: 30, y: -20), expected: .level0Index(2)),
+ .init(point: CGPoint(x: 32, y: 0), expected: .level0Index(2)),
+ .init(point: CGPoint(x: 32, y: 8), expected: .level0Index(2)),
+ .init(point: CGPoint(x: 32, y: 40), expected: .level0Index(2)),
+ .init(point: CGPoint(x: 32, y: -20), expected: .level0Index(2)),
+ .init(
+ point: CGPoint(x: 33, y: 0),
+ expected: MTMathListIndex(
+ atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)),
+ .init(
+ point: CGPoint(x: 33, y: 8),
+ expected: MTMathListIndex(
+ atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)),
+ .init(
+ point: CGPoint(x: 33, y: 40),
+ expected: MTMathListIndex(
+ atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)),
+ .init(
+ point: CGPoint(x: 33, y: -20),
+ expected: MTMathListIndex(
+ atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)),
+ .init(
+ point: CGPoint(x: 35, y: 0),
+ expected: MTMathListIndex(
+ atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)),
+ .init(
+ point: CGPoint(x: 35, y: 8),
+ expected: MTMathListIndex(
+ atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)),
+ .init(
+ point: CGPoint(x: 35, y: 40),
+ expected: MTMathListIndex(
+ atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeNumerator)),
+ .init(
+ point: CGPoint(x: 35, y: -20),
+ expected: MTMathListIndex(
+ atLocation: 2, withSubIndex: .level0Index(0), type: .subIndexTypeDenominator)),
+]
+
+private let fractionPlusRegularCases: [ClosestIndexCase] = [
+ .init(point: CGPoint(x: 15, y: 0), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 15, y: 8), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 15, y: 40), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 15, y: -20), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 13, y: 0), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 13, y: 8), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 13, y: 40), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 13, y: -20), expected: .level0Index(1)),
+ .init(
+ point: CGPoint(x: 11, y: 0),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)),
+ .init(
+ point: CGPoint(x: 11, y: 8),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)),
+ .init(
+ point: CGPoint(x: 11, y: 40),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)),
+ .init(
+ point: CGPoint(x: 11, y: -20),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)),
+ .init(
+ point: CGPoint(x: 9, y: 0),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)),
+ .init(
+ point: CGPoint(x: 9, y: 8),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)),
+ .init(
+ point: CGPoint(x: 9, y: 40),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNumerator)),
+ .init(
+ point: CGPoint(x: 9, y: -20),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeDenominator)),
+]
+
+private let exponentCases: [ClosestIndexCase] = [
+ .init(point: CGPoint(x: -10, y: 8), expected: .level0Index(0)),
+ .init(point: CGPoint(x: -10, y: 0), expected: .level0Index(0)),
+ .init(point: CGPoint(x: -10, y: 40), expected: .level0Index(0)),
+ .init(point: CGPoint(x: -10, y: -20), expected: .level0Index(0)),
+ .init(point: CGPoint(x: 0, y: 0), expected: .level0Index(0)),
+ .init(point: CGPoint(x: 0, y: 8), expected: .level0Index(0)),
+ .init(point: CGPoint(x: 0, y: 40), expected: .level0Index(0)),
+ .init(point: CGPoint(x: 0, y: -20), expected: .level0Index(0)),
+ .init(
+ point: CGPoint(x: 9, y: 0),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)),
+ .init(
+ point: CGPoint(x: 9, y: 8),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)),
+ .init(
+ point: CGPoint(x: 9, y: 40),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeSuperscript)),
+ .init(
+ point: CGPoint(x: 9, y: -20),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)),
+ .init(
+ point: CGPoint(x: 10, y: 0),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)),
+ .init(
+ point: CGPoint(x: 10, y: 8),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)),
+ .init(
+ point: CGPoint(x: 10, y: 40),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeSuperscript)),
+ .init(
+ point: CGPoint(x: 10, y: -20),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)),
+ .init(
+ point: CGPoint(x: 11, y: 0),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)),
+ .init(
+ point: CGPoint(x: 11, y: 8),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeSuperscript)),
+ .init(
+ point: CGPoint(x: 11, y: 40),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(0), type: .subIndexTypeSuperscript)),
+ .init(
+ point: CGPoint(x: 11, y: -20),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeNucleus)),
+ .init(point: CGPoint(x: 17, y: 0), expected: .level0Index(1)),
+ .init(
+ point: CGPoint(x: 17, y: 8),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeSuperscript)),
+ .init(
+ point: CGPoint(x: 17, y: 40),
+ expected: MTMathListIndex(
+ atLocation: 0, withSubIndex: .level0Index(1), type: .subIndexTypeSuperscript)),
+ .init(point: CGPoint(x: 17, y: -20), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 30, y: 0), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 30, y: 8), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 30, y: 40), expected: .level0Index(1)),
+ .init(point: CGPoint(x: 30, y: -20), expected: .level0Index(1)),
+]
diff --git a/Tests/MathEditorSwiftTests/MTEditableMathLabelSwiftTests.swift b/Tests/MathEditorSwiftTests/MTEditableMathLabelSwiftTests.swift
new file mode 100644
index 0000000..97d5610
--- /dev/null
+++ b/Tests/MathEditorSwiftTests/MTEditableMathLabelSwiftTests.swift
@@ -0,0 +1,69 @@
+import Testing
+import iosMath
+
+@testable import MathEditorSwift
+
+@Suite(.serialized)
+@MainActor
+struct MTEditableMathLabelSwiftTests {
+
+ @Test("insertMathList inserts the provided math list into an empty label")
+ func insertMathListIntoEmptyLabel() {
+ let label = MTEditableMathLabelSwift()
+ let insertedList = MTMathListBuilder.build(from: "1+x+\\frac{1}{2}")!
+ label.insertMathList(insertedList, at: .zero)
+ expectLatex("1+x+\\frac{1}{2}", from: label.mathList)
+ }
+
+ @Test("insertText when insertionIndex is temporarily nil")
+ func insertTextWhenInsertionIndexIsNil() throws {
+ for (input, expected) in [
+ ("x", "x"),
+ ("()", "()"),
+ ("||", "||"),
+ ("^", "□^{□}"),
+ ("/", "\\atop{1}{□}"),
+ ("_", "□_{□}"),
+ (MTSymbolSquareRoot, "\\sqrt{□}"),
+ (MTSymbolFractionSlash, "\\atop{□}{□}"),
+ (MTSymbolCubeRoot, "\\sqrt[□]{□}"),
+ ] {
+ let label = makeLabelWithNilInsertionIndex()
+ label.insertText(input)
+ expectStringValue(of: label.mathList, to: expected)
+ }
+ }
+
+ @Test("insertText when insertionIndex is temporarily nil and label is not empty")
+ func appendTextWhenInsertionIndexIsNil() throws {
+ for (input, expected) in [
+ ("x", "1+x"),
+ ("()", "1+()"),
+ ("||", "1+||"),
+ ("^", "1+^{□}"),
+ ("/", "1+\\atop{1}{□}"),
+ ("_", "1+_{□}"),
+ (MTSymbolSquareRoot, "1+\\sqrt{□}"),
+ (MTSymbolFractionSlash, "1+\\atop{□}{□}"),
+ (MTSymbolCubeRoot, "1+\\sqrt[□]{□}"),
+ ] {
+ let mathList = MTMathListBuilder.build(from: "1+")!
+ let label = makeLabelWithNilInsertionIndex(mathList: mathList)
+ label.insertText(input)
+ expectStringValue(of: label.mathList, to: expected)
+ }
+ }
+
+}
+
+private func makeLabelWithNilInsertionIndex(mathList: MTMathList? = nil) -> MTEditableMathLabelSwift
+{
+ let label = MTEditableMathLabelSwift(frame: .zero)
+ if let mathList {
+ label.mathList = mathList
+ }
+ let tapSelector = NSSelectorFromString("tap:")
+ #expect(label.responds(to: tapSelector))
+ _ = label.perform(tapSelector, with: nil)
+ return label
+}
diff --git a/Tests/MathEditorSwiftTests/MTMathListEditingTests.swift b/Tests/MathEditorSwiftTests/MTMathListEditingTests.swift
new file mode 100644
index 0000000..e344341
--- /dev/null
+++ b/Tests/MathEditorSwiftTests/MTMathListEditingTests.swift
@@ -0,0 +1,512 @@
+import Testing
+import iosMath
+
+// Switch the implementation under test by toggling these imports.
+@testable import MathEditorSwift
+
+@MainActor
+@Suite(.serialized)
+struct MTMathListEditingTests {
+ @Test("insert at top level adds an atom at the requested index")
+ func insertTopLevelAtom() {
+ let mathList = list("1+3")
+
+ mathList.insert(atom("2"), atListIndex: .level0Index(2))
+
+ expectLatex("1+23", from: mathList)
+ }
+
+ @Test("insert at nucleus transfers scripts onto the inserted atom")
+ func insertAtNucleusTransfersScripts() {
+ let mathList = list("x^2")
+ mathList.insert(
+ atom("y"),
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(1),
+ type: .subIndexTypeNucleus
+ )
+ )
+
+ expectLatex("xy^{2}", from: mathList)
+ }
+
+ @Test("insert recurses into a fraction numerator")
+ func insertIntoFractionNumerator() {
+ let mathList = list("\\frac{3}{2}")
+ mathList.insert(
+ atom("x"),
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(1),
+ type: .subIndexTypeNumerator
+ )
+ )
+
+ expectLatex("\\frac{3x}{2}", from: mathList)
+ }
+
+ @Test("insert recurses into a radical degree")
+ func insertIntoRadicalDegree() {
+ let mathList = list("\\sqrt[3]{x}")
+ mathList.insert(
+ atom("n"),
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(1),
+ type: .subIndexTypeDegree
+ )
+ )
+
+ expectLatex("\\sqrt[3n]{x}", from: mathList)
+ }
+
+ @Test("insert also recurses into radicands, denominators, subscripts, and superscripts")
+ func insertIntoOtherNestedLists() {
+ let radicand = list("\\sqrt{x}")
+ radicand.insert(
+ atom("y"),
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(1),
+ type: .subIndexTypeRadicand
+ )
+ )
+ expectLatex("\\sqrt{xy}", from: radicand)
+
+ let denominator = list("\\frac{3}{2}")
+ denominator.insert(
+ atom("x"),
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(1),
+ type: .subIndexTypeDenominator
+ )
+ )
+ expectLatex("\\frac{3}{2x}", from: denominator)
+
+ let subscriptList = list("x_1")
+ subscriptList.insert(
+ atom("0"),
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(1),
+ type: .subIndexTypeSubscript
+ )
+ )
+ expectLatex("x_{10}", from: subscriptList)
+
+ let superscript = list("x^2")
+ superscript.insert(
+ atom("3"),
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(1),
+ type: .subIndexTypeSuperscript
+ )
+ )
+ expectLatex("x^{23}", from: superscript)
+ }
+
+ @Test("insert is a no-op for inner indices and missing subindices")
+ func insertNoOpsForInnerAndMissingSubindices() {
+ let inner = list("12")
+ inner.insert(
+ atom("9"),
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(0),
+ type: .subIndexTypeInner
+ )
+ )
+ expectLatex("12", from: inner)
+
+ let nucleusWithoutSubindex = list("x^2")
+ nucleusWithoutSubindex.insert(
+ atom("y"),
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: nil,
+ type: .subIndexTypeNucleus
+ )
+ )
+ expectLatex("x^{2}", from: nucleusWithoutSubindex)
+
+ let denominatorWithoutSubindex = list("\\frac{1}{2}")
+ denominatorWithoutSubindex.insert(
+ atom("3"),
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: nil,
+ type: .subIndexTypeDenominator
+ )
+ )
+ expectLatex("\\frac{1}{2}", from: denominatorWithoutSubindex)
+ }
+
+ @Test("remove at nucleus fuses scripts into the previous atom when possible")
+ func removeAtNucleusFusesIntoPreviousAtom() {
+ let mathList = list("xy^2")
+ mathList.removeAtom(
+ atListIndex: MTMathListIndex(
+ atLocation: 1,
+ withSubIndex: .level0Index(1),
+ type: .subIndexTypeNucleus
+ )
+ )
+
+ expectLatex("x^{2}", from: mathList)
+ }
+
+ @Test("remove at nucleus empties the current atom when it cannot fuse")
+ func removeAtNucleusEmptiesCurrentAtomWhenFusionIsNotPossible() throws {
+ let mathList = list("x^2")
+ mathList.removeAtom(
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(1),
+ type: .subIndexTypeNucleus
+ )
+ )
+
+ let currentAtom = try #require(mathList.atom(atListIndex: .level0Index(0)))
+ #expect(currentAtom.nucleus.isEmpty)
+ #expect(currentAtom.superScript != nil)
+ expectLatex("{}^{2}", from: mathList)
+ }
+
+ @Test(
+ "remove atom also recurses through top level, fractions, radicals, subscripts, and superscripts"
+ )
+ func removeAtomFromOtherNestedLists() {
+ let topLevel = list("123")
+ topLevel.removeAtom(atListIndex: .level0Index(1))
+ expectLatex("13", from: topLevel)
+
+ let numerator = list("\\frac{34}{2}")
+ numerator.removeAtom(
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(1),
+ type: .subIndexTypeNumerator
+ )
+ )
+ expectLatex("\\frac{3}{2}", from: numerator)
+
+ let degree = list("\\sqrt[34]{x}")
+ degree.removeAtom(
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(1),
+ type: .subIndexTypeDegree
+ )
+ )
+ expectLatex("\\sqrt[3]{x}", from: degree)
+
+ let radicand = list("\\sqrt{xy}")
+ radicand.removeAtom(
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(1),
+ type: .subIndexTypeRadicand
+ )
+ )
+ expectLatex("\\sqrt{x}", from: radicand)
+
+ let subscriptList = list("x_{12}")
+ subscriptList.removeAtom(
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(1),
+ type: .subIndexTypeSubscript
+ )
+ )
+ expectLatex("x_{1}", from: subscriptList)
+
+ let superscript = list("x^{23}")
+ superscript.removeAtom(
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(1),
+ type: .subIndexTypeSuperscript
+ )
+ )
+ expectLatex("x^{2}", from: superscript)
+ }
+
+ @Test("remove atom is a no-op for inner indices and missing subindices")
+ func removeAtomNoOpsForInnerAndMissingSubindices() {
+ let inner = list("12")
+ inner.removeAtom(
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(0),
+ type: .subIndexTypeInner
+ )
+ )
+ expectLatex("12", from: inner)
+
+ let numeratorWithoutSubindex = list("\\frac{12}{3}")
+ numeratorWithoutSubindex.removeAtom(
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: nil,
+ type: .subIndexTypeNumerator
+ )
+ )
+ expectLatex("\\frac{12}{3}", from: numeratorWithoutSubindex)
+
+ let superscriptWithoutSubindex = list("x^2")
+ superscriptWithoutSubindex.removeAtom(
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: nil,
+ type: .subIndexTypeSuperscript
+ )
+ )
+ expectLatex("x^{2}", from: superscriptWithoutSubindex)
+ }
+
+ @Test("remove atoms recurses into a denominator range")
+ func removeAtomsFromDenominatorRange() {
+ let mathList = list("\\frac{12}{345}")
+ mathList.removeAtoms(
+ inListIndexRange: .make(
+ MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(1),
+ type: .subIndexTypeDenominator
+ ),
+ length: 2
+ )
+ )
+
+ expectLatex("\\frac{12}{3}", from: mathList)
+ }
+
+ @Test("remove atoms also supports top level, numerators, radicals, subscripts, and superscripts")
+ func removeAtomsFromOtherRanges() {
+ let topLevel = list("1234")
+ topLevel.removeAtoms(inListIndexRange: .make(.level0Index(1), length: 2))
+ expectLatex("14", from: topLevel)
+
+ let numerator = list("\\frac{123}{45}")
+ numerator.removeAtoms(
+ inListIndexRange: .make(
+ MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(1),
+ type: .subIndexTypeNumerator
+ ),
+ length: 2
+ )
+ )
+ expectLatex("\\frac{1}{45}", from: numerator)
+
+ let degree = list("\\sqrt[123]{x}")
+ degree.removeAtoms(
+ inListIndexRange: .make(
+ MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(1),
+ type: .subIndexTypeDegree
+ ),
+ length: 2
+ )
+ )
+ expectLatex("\\sqrt[1]{x}", from: degree)
+
+ let radicand = list("\\sqrt{xyz}")
+ radicand.removeAtoms(
+ inListIndexRange: .make(
+ MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(1),
+ type: .subIndexTypeRadicand
+ ),
+ length: 2
+ )
+ )
+ expectLatex("\\sqrt{x}", from: radicand)
+
+ let subscriptList = list("x_{123}")
+ subscriptList.removeAtoms(
+ inListIndexRange: .make(
+ MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(1),
+ type: .subIndexTypeSubscript
+ ),
+ length: 2
+ )
+ )
+ expectLatex("x_{1}", from: subscriptList)
+
+ let superscript = list("x^{123}")
+ superscript.removeAtoms(
+ inListIndexRange: .make(
+ MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(1),
+ type: .subIndexTypeSuperscript
+ ),
+ length: 2
+ )
+ )
+ expectLatex("x^{1}", from: superscript)
+ }
+
+ @Test("remove atoms is a no-op for inner indices")
+ func removeAtomsNoOpForInnerIndex() {
+ let inner = list("123")
+ inner.removeAtoms(
+ inListIndexRange: .make(
+ MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(0),
+ type: .subIndexTypeInner
+ ),
+ length: 1
+ )
+ )
+ expectLatex("123", from: inner)
+ }
+
+ @Test("atom at list index resolves nested atoms")
+ func atomAtListIndexResolvesNestedAtoms() throws {
+ let mathList = list("\\sqrt[3]{x_2}+y")
+
+ let degree = try #require(
+ mathList.atom(
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(0),
+ type: .subIndexTypeDegree
+ ))
+ )
+ #expect(degree.nucleus == "3")
+
+ let subscriptAtom = try #require(
+ mathList.atom(
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(0),
+ type: .subIndexTypeSubscript
+ ),
+ type: .subIndexTypeRadicand
+ ))
+ )
+ #expect(subscriptAtom.nucleus == "2")
+
+ let superscriptAtom = try #require(
+ mathList.atom(
+ atListIndex: MTMathListIndex(
+ atLocation: 2,
+ withSubIndex: .level0Index(0),
+ type: .subIndexTypeNucleus
+ ))
+ )
+ #expect(superscriptAtom.nucleus == "y")
+
+ #expect(mathList.atom(atListIndex: .level0Index(99)) == nil)
+ #expect(mathList.atom(atListIndex: nil) == nil)
+ }
+
+ @Test(
+ "atom at list index also resolves fraction and superscript atoms and returns nil for the wrong container type"
+ )
+ func atomAtListIndexCoversRemainingContainerTypes() throws {
+ let mathList = list("\\frac{1}{x^2}")
+
+ let numerator = try #require(
+ mathList.atom(
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(0),
+ type: .subIndexTypeNumerator
+ ))
+ )
+ #expect(numerator.nucleus == "1")
+
+ let superscript = try #require(
+ mathList.atom(
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(0),
+ type: .subIndexTypeSuperscript
+ ),
+ type: .subIndexTypeDenominator
+ ))
+ )
+ #expect(superscript.nucleus == "2")
+
+ #expect(
+ list("x").atom(
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(0),
+ type: .subIndexTypeNumerator
+ )) == nil
+ )
+ #expect(
+ list("x").atom(
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(0),
+ type: .subIndexTypeDegree
+ )) == nil
+ )
+ }
+
+ @Test("atom at list index returns nil for inner indices and missing subindices")
+ func atomAtListIndexReturnsNilForInnerAndMissingSubindices() {
+ #expect(
+ list("x").atom(
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: .level0Index(0),
+ type: .subIndexTypeInner
+ )) == nil
+ )
+
+ #expect(
+ list("x^2").atom(
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: nil,
+ type: .subIndexTypeSuperscript
+ )) == nil
+ )
+
+ #expect(
+ list("\\frac{1}{2}").atom(
+ atListIndex: MTMathListIndex(
+ atLocation: 0,
+ withSubIndex: nil,
+ type: .subIndexTypeNumerator
+ )) == nil
+ )
+ }
+
+ @Test
+ @MainActor
+ func insertOutOfBoundsFail() async throws {
+ await #expect(processExitsWith: .failure) {
+ let mathList = list("12")
+ let _ = mathList.insert(atom("3"), atListIndex: .level0Index(3))
+ return
+ }
+ }
+}
+
+private func list(_ latex: String) -> MTMathList {
+ MTMathListBuilder.build(from: latex)!
+}
+
+private func atom(_ latex: String) -> MTMathAtom {
+ list(latex).atoms[0]
+}
diff --git a/format.sh b/format.sh
new file mode 100755
index 0000000..0de1175
--- /dev/null
+++ b/format.sh
@@ -0,0 +1,26 @@
+set -euo pipefail
+
+clang_format() {
+ local dir=$1
+ echo "Formatting $dir\n\n"
+ for file in $(find $dir -name '*.h' -or -name '*.m' -or -name '*.mm'); do
+ echo "Formatting $file"
+ clang-format $file -i
+ done
+}
+
+format() {
+ local dir=$1
+ echo "Formatting $dir"
+ swift format --in-place --parallel -r $dir
+}
+
+lint() {
+ local dir=$1
+ echo "Linting $dir"
+ swift format lint --strict --parallel -r $dir
+}
+
+format .
+lint .
+
diff --git a/regressions.md b/regressions.md
new file mode 100644
index 0000000..b8b23f3
--- /dev/null
+++ b/regressions.md
@@ -0,0 +1,60 @@
+# Swift vs Objective-C regression review
+
+## Scope
+Compared `MTEditableMathLabelSwift` against legacy `MTEditableMathLabel` implementation in Objective-C.
+
+## Status update (April 1, 2026)
+The previously reported guard/early-return insert regressions have been resolved in Swift by consistently using a fallback insertion index (`resolvedInsertionIndex()`) and by applying display invalidation directly to `label`.
+
+## Resolved regressions
+
+1. `insertMathList(_:at:)` no longer drops inserts when no closest index is available.
+2. Normal typed input no longer no-ops when `insertionIndex` is `nil`.
+3. Special insert operations (`^`, `_`, `/`, radicals, paired inserts) no longer no-op when `insertionIndex` is `nil`.
+4. Paired shortcuts (`"()"`, `"||"`) no longer drop due to nil-index early return.
+5. First-key race after tap-to-edit no longer drops operators due to nil-index guards.
+6. Highlight redraw now invalidates `MTMathUILabel` directly.
+7. Clearing highlights now relayouts the inner label directly.
+8. `textColor` setter no longer coalesces nil to current color (the prior no-op behavior is removed).
+
+## Active regressions / parity gaps
+
+### 1) Swift class cannot be initialized from nib/storyboard (`init(coder:)` unavailable)
+- **Swift behavior**: `init(coder:)` is unavailable and traps.
+- **Legacy Objective-C behavior**: Supports archive-based initialization via `awakeFromNib`.
+- **Impact**: Existing nib/storyboard integrations that worked with `MTEditableMathLabel` are not drop-in compatible with `MTEditableMathLabelSwift`.
+- **Relevant files**:
+ - `MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift`
+ - `mathEditor/editor/MTEditableMathLabel.m`
+
+### 2) Tap-while-editing shows caret handle (legacy hides it)
+- **Swift behavior**: In `handleTap(at:)`, already-editing taps call `caretView.showHandle(true)`.
+- **Legacy Objective-C behavior**: `handleTapAtPoint:` calls `[_caretView showHandle:NO]` in the same branch.
+- **Impact**: Visible interaction behavior and hit area differ from legacy.
+- **Relevant files**:
+ - `MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift`
+ - `mathEditor/editor/MTEditableMathLabel.m`
+
+### 3) Objective-C integration points for `delegate`/`keyboard` are not API-compatible
+- **Swift behavior**: `delegate` and `keyboard` are Swift protocol-typed properties using non-`@objc` protocols.
+- **Legacy Objective-C behavior**: Public ObjC properties use ObjC protocols (`id`, `MTView*`).
+- **Impact**: Legacy ObjC hosts cannot use the same delegate/keyboard contracts as a drop-in migration.
+- **Relevant files**:
+ - `MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift`
+ - `mathEditor/editor/MTEditableMathLabel.h`
+
+### 4) `mathList = nil` clear semantics are not preserved in Swift API
+- **Swift behavior**: `mathList` is non-optional and cannot be assigned `nil` in Swift.
+- **Legacy Objective-C behavior**: `setMathList:` treats `nil` as clear and replaces with a new empty `MTMathList`.
+- **Impact**: Hosts depending on nil-assignment clear behavior must switch to `clear()` or equivalent logic.
+- **Relevant files**:
+ - `MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift`
+ - `mathEditor/editor/MTEditableMathLabel.m`
+
+### 5) Keyboard `equalsAllowed` behavior differs from legacy
+- **Swift behavior**: `setKeyboardMode()` resets `keyboard?.equalsAllowed = true` and then disables in superscript, numerator, and denominator.
+- **Legacy Objective-C behavior**: No unconditional reset; denominator disabling remains commented out.
+- **Impact**: `=` availability can diverge from legacy based on cursor transitions and denominator context.
+- **Relevant files**:
+ - `MathEditorSwift/Sources/MathEditorSwift/MTEditableMathLabelSwift.swift`
+ - `mathEditor/editor/MTEditableMathLabel.m`
diff --git a/todo.md b/todo.md
new file mode 100644
index 0000000..756585c
--- /dev/null
+++ b/todo.md
@@ -0,0 +1,27 @@
+
+## MTCaretView
+
+1. - [x] No need for baseColor vs color
+2. - [x] setNeedsDisplayCompat
+3. - [x] handleDrag convert is different implementation
+4. - [x] no mouse cancelled
+5. - [x] can caretColor be non-optional
+6. - [x] Can caret handle initialized with label
+7. - [x] is startBlinkingIfNeeded correct?
+8. - [x] Rename MTCaretHandleSwift to CaretHandle
+
+## MTEditableMathlabelSwift
+
+1. - [x] MTKeyInputSwift should be alias to UIKeyInput
+2. - [x] Bring comments back
+3. - [ ] highlightColor is different in `initialize`
+4. - [x] layoutLabelIfNeeded should be the same as objc
+5. - [x] setNeedsDisplayCompat should be changed
+6. - [x] setLabelNeedsDisplayCompat should be changed
+7. - [ ] getIndexAfterSpecialStructure: is next should unwrapped?
+
+### Responder
+
+1. - [x] Should not extend UIKeyInput
+2. - [x] reuse becomeFirstResponder on both
+3. - [x] Reuse resignFirstResponder on both