[quick_actions]Migrates all remaining components to Swift, and deprecate OCMock (#6597)

* [quick_actions]migrate shortcut state manager, deprecate OCMock and use POP

* remove objc proj settings

* rename shortcut state manager

* bump version

* run swift-format

* nit

* remove public_header_files

* use shortcut item parser instead of shortcut state manager

* some nit

* rename AppShortcutControlling to ShortcutItemProviding

* nit

* do not crash if no type or title

* update license
diff --git a/packages/quick_actions/quick_actions_ios/CHANGELOG.md b/packages/quick_actions/quick_actions_ios/CHANGELOG.md
index 31fe438..bded354 100644
--- a/packages/quick_actions/quick_actions_ios/CHANGELOG.md
+++ b/packages/quick_actions/quick_actions_ios/CHANGELOG.md
@@ -1,5 +1,6 @@
-## NEXT
+## 1.0.2
 
+* Migrates remaining components to Swift and removes all Objective-C settings.
 * Migrates `RunnerUITests` to Swift.
 
 ## 1.0.1
diff --git a/packages/quick_actions/quick_actions_ios/example/ios/Podfile b/packages/quick_actions/quick_actions_ios/example/ios/Podfile
index b528052..3924e59 100644
--- a/packages/quick_actions/quick_actions_ios/example/ios/Podfile
+++ b/packages/quick_actions/quick_actions_ios/example/ios/Podfile
@@ -31,7 +31,6 @@
   flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
   target 'RunnerTests' do
     inherit! :search_paths
-    pod 'OCMock', '~> 3.9.1'
   end
 end
 
diff --git a/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj b/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj
index c853a19..f5b708b 100644
--- a/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj
@@ -16,9 +16,12 @@
 		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
 		97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
 		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
+		E092A7ED28D10802005C7F67 /* MockMethodChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7EA28D10801005C7F67 /* MockMethodChannel.swift */; };
+		E092A7EE28D10802005C7F67 /* QuickActionsPluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */; };
+		E092A7F128D10890005C7F67 /* MockShortcutItemProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F028D10890005C7F67 /* MockShortcutItemProvider.swift */; };
+		E092A7F428D110B3005C7F67 /* DefaultShortcutItemParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F328D110B3005C7F67 /* DefaultShortcutItemParserTests.swift */; };
 		E092A7F628D128EB005C7F67 /* RunnerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F528D128EB005C7F67 /* RunnerUITests.swift */; };
-		E0C09C29289C729D00E6977E /* FLTQuickActionsPluginTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C09C28289C729D00E6977E /* FLTQuickActionsPluginTests.m */; };
-		E0C09C32289DBFCA00E6977E /* FLTShortcutStateManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C09C31289DBFCA00E6977E /* FLTShortcutStateManagerTests.m */; };
+		E0A075D529147FE200329BAE /* MockShortcutItemParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0A075D429147FE200329BAE /* MockShortcutItemParser.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -75,9 +78,12 @@
 		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		9D27FE1F0F21D4D47DDA16DE /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
 		C35AD3650AB6BF850E016715 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+		E092A7EA28D10801005C7F67 /* MockMethodChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockMethodChannel.swift; sourceTree = "<group>"; };
+		E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickActionsPluginTests.swift; sourceTree = "<group>"; };
+		E092A7F028D10890005C7F67 /* MockShortcutItemProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockShortcutItemProvider.swift; sourceTree = "<group>"; };
+		E092A7F328D110B3005C7F67 /* DefaultShortcutItemParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultShortcutItemParserTests.swift; sourceTree = "<group>"; };
 		E092A7F528D128EB005C7F67 /* RunnerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerUITests.swift; sourceTree = "<group>"; };
-		E0C09C28289C729D00E6977E /* FLTQuickActionsPluginTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTQuickActionsPluginTests.m; sourceTree = "<group>"; };
-		E0C09C31289DBFCA00E6977E /* FLTShortcutStateManagerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTShortcutStateManagerTests.m; sourceTree = "<group>"; };
+		E0A075D429147FE200329BAE /* MockShortcutItemParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockShortcutItemParser.swift; sourceTree = "<group>"; };
 		F0609304FBCAEC2289164BD5 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
@@ -111,9 +117,10 @@
 		33E20B3326EFCDFC00A4A191 /* RunnerTests */ = {
 			isa = PBXGroup;
 			children = (
+				E092A7F228D10908005C7F67 /* Mocks */,
 				33E20B3626EFCDFC00A4A191 /* Info.plist */,
-				E0C09C31289DBFCA00E6977E /* FLTShortcutStateManagerTests.m */,
-				E0C09C28289C729D00E6977E /* FLTQuickActionsPluginTests.m */,
+				E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */,
+				E092A7F328D110B3005C7F67 /* DefaultShortcutItemParserTests.swift */,
 			);
 			path = RunnerTests;
 			sourceTree = "<group>";
@@ -205,6 +212,16 @@
 			name = Pods;
 			sourceTree = "<group>";
 		};
+		E092A7F228D10908005C7F67 /* Mocks */ = {
+			isa = PBXGroup;
+			children = (
+				E092A7EA28D10801005C7F67 /* MockMethodChannel.swift */,
+				E092A7F028D10890005C7F67 /* MockShortcutItemProvider.swift */,
+				E0A075D429147FE200329BAE /* MockShortcutItemParser.swift */,
+			);
+			path = Mocks;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
@@ -277,6 +294,7 @@
 				TargetAttributes = {
 					33E20B3126EFCDFC00A4A191 = {
 						CreatedOnToolsVersion = 12.5;
+						LastSwiftMigration = 1330;
 						TestTargetID = 97C146ED1CF9000F007C117D;
 					};
 					686BE82C25E58CCF00862533 = {
@@ -416,8 +434,11 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				E0C09C32289DBFCA00E6977E /* FLTShortcutStateManagerTests.m in Sources */,
-				E0C09C29289C729D00E6977E /* FLTQuickActionsPluginTests.m in Sources */,
+				E092A7EE28D10802005C7F67 /* QuickActionsPluginTests.swift in Sources */,
+				E092A7ED28D10802005C7F67 /* MockMethodChannel.swift in Sources */,
+				E092A7F128D10890005C7F67 /* MockShortcutItemProvider.swift in Sources */,
+				E0A075D529147FE200329BAE /* MockShortcutItemParser.swift in Sources */,
+				E092A7F428D110B3005C7F67 /* DefaultShortcutItemParserTests.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -479,6 +500,7 @@
 			baseConfigurationReference = 9D27FE1F0F21D4D47DDA16DE /* Pods-RunnerTests.debug.xcconfig */;
 			buildSettings = {
 				BUNDLE_LOADER = "$(TEST_HOST)";
+				CLANG_ENABLE_MODULES = YES;
 				INFOPLIST_FILE = RunnerTests/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
@@ -487,6 +509,8 @@
 				);
 				PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests;
 				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+				SWIFT_VERSION = 5.0;
 				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner";
 			};
 			name = Debug;
@@ -496,6 +520,7 @@
 			baseConfigurationReference = 96F949A6B78E2DC62B93C4F8 /* Pods-RunnerTests.release.xcconfig */;
 			buildSettings = {
 				BUNDLE_LOADER = "$(TEST_HOST)";
+				CLANG_ENABLE_MODULES = YES;
 				INFOPLIST_FILE = RunnerTests/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
@@ -504,6 +529,7 @@
 				);
 				PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests;
 				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_VERSION = 5.0;
 				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner";
 			};
 			name = Release;
diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutItemParserTests.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutItemParserTests.swift
new file mode 100644
index 0000000..739f88e
--- /dev/null
+++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutItemParserTests.swift
@@ -0,0 +1,67 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import Flutter
+import XCTest
+
+@testable import quick_actions_ios
+
+class DefaultShortcutItemParserTests: XCTestCase {
+
+  func testParseShortcutItems() {
+    let rawItem = [
+      "type": "SearchTheThing",
+      "localizedTitle": "Search the thing",
+      "icon": "search_the_thing.png",
+    ]
+
+    let expectedItem = UIApplicationShortcutItem(
+      type: "SearchTheThing",
+      localizedTitle: "Search the thing",
+      localizedSubtitle: nil,
+      icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"),
+      userInfo: nil)
+
+    let parser = DefaultShortcutItemParser()
+    XCTAssertEqual(parser.parseShortcutItems([rawItem]), [expectedItem])
+  }
+
+  func testParseShortcutItems_noIcon() {
+    let rawItem: [String: Any] = [
+      "type": "SearchTheThing",
+      "localizedTitle": "Search the thing",
+      "icon": NSNull(),
+    ]
+
+    let expectedItem = UIApplicationShortcutItem(
+      type: "SearchTheThing",
+      localizedTitle: "Search the thing",
+      localizedSubtitle: nil,
+      icon: nil,
+      userInfo: nil)
+
+    let parser = DefaultShortcutItemParser()
+    XCTAssertEqual(parser.parseShortcutItems([rawItem]), [expectedItem])
+  }
+
+  func testParseShortcutItems_noType() {
+    let rawItem = [
+      "localizedTitle": "Search the thing",
+      "icon": "search_the_thing.png",
+    ]
+
+    let parser = DefaultShortcutItemParser()
+    XCTAssertEqual(parser.parseShortcutItems([rawItem]), [])
+  }
+
+  func testParseShortcutItems_noLocalizedTitle() {
+    let rawItem = [
+      "type": "SearchTheThing",
+      "icon": "search_the_thing.png",
+    ]
+
+    let parser = DefaultShortcutItemParser()
+    XCTAssertEqual(parser.parseShortcutItems([rawItem]), [])
+  }
+}
diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/FLTQuickActionsPluginTests.m b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/FLTQuickActionsPluginTests.m
deleted file mode 100644
index 89651b5..0000000
--- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/FLTQuickActionsPluginTests.m
+++ /dev/null
@@ -1,210 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-@import Flutter;
-@import quick_actions_ios;
-@import XCTest;
-#import <OCMock/OCMock.h>
-
-@interface FLTQuickActionsPluginTests : XCTestCase
-
-@end
-
-@implementation FLTQuickActionsPluginTests
-
-- (void)testHandleMethodCall_setShortcutItems {
-  NSDictionary *rawItem = @{
-    @"type" : @"SearchTheThing",
-    @"localizedTitle" : @"Search the thing",
-    @"icon" : @"search_the_thing.png",
-  };
-
-  FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"setShortcutItems"
-                                                              arguments:@[ rawItem ]];
-
-  FLTShortcutStateManager *mockShortcutStateManager = OCMClassMock([FLTShortcutStateManager class]);
-
-  QuickActionsPlugin *plugin =
-      [[QuickActionsPlugin alloc] initWithChannel:OCMClassMock([FlutterMethodChannel class])
-                             shortcutStateManager:mockShortcutStateManager];
-  XCTestExpectation *resultExpectation =
-      [self expectationWithDescription:@"result block must be called."];
-  [plugin handleMethodCall:call
-                    result:^(id _Nullable result) {
-                      XCTAssertNil(result, @"result block must be called with nil.");
-                      [resultExpectation fulfill];
-                    }];
-  [self waitForExpectationsWithTimeout:1 handler:nil];
-
-  OCMVerify([mockShortcutStateManager setShortcutItems:@[ rawItem ]]);
-}
-
-- (void)testHandleMethodCall_clearShortcutItems {
-  FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"clearShortcutItems"
-                                                              arguments:nil];
-  FLTShortcutStateManager *mockShortcutStateManager = OCMClassMock([FLTShortcutStateManager class]);
-  QuickActionsPlugin *plugin =
-      [[QuickActionsPlugin alloc] initWithChannel:OCMClassMock([FlutterMethodChannel class])
-                             shortcutStateManager:mockShortcutStateManager];
-  XCTestExpectation *resultExpectation =
-      [self expectationWithDescription:@"result block must be called."];
-  [plugin handleMethodCall:call
-                    result:^(id _Nullable result) {
-                      XCTAssertNil(result, @"result block must be called with nil.");
-                      [resultExpectation fulfill];
-                    }];
-  [self waitForExpectationsWithTimeout:1 handler:nil];
-  OCMVerify([mockShortcutStateManager setShortcutItems:@[]]);
-}
-
-- (void)testHandleMethodCall_getLaunchAction {
-  FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"getLaunchAction"
-                                                              arguments:nil];
-
-  QuickActionsPlugin *plugin =
-      [[QuickActionsPlugin alloc] initWithChannel:OCMClassMock([FlutterMethodChannel class])
-                             shortcutStateManager:OCMClassMock([FLTShortcutStateManager class])];
-  XCTestExpectation *resultExpectation =
-      [self expectationWithDescription:@"result block must be called."];
-  [plugin handleMethodCall:call
-                    result:^(id _Nullable result) {
-                      XCTAssertNil(result, @"result block must be called with nil.");
-                      [resultExpectation fulfill];
-                    }];
-  [self waitForExpectationsWithTimeout:1 handler:nil];
-}
-
-- (void)testHandleMethodCall_nonExistMethods {
-  FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"nonExist" arguments:nil];
-
-  QuickActionsPlugin *plugin =
-      [[QuickActionsPlugin alloc] initWithChannel:OCMClassMock([FlutterMethodChannel class])
-                             shortcutStateManager:OCMClassMock([FLTShortcutStateManager class])];
-  XCTestExpectation *resultExpectation =
-      [self expectationWithDescription:@"result must be called."];
-  [plugin
-      handleMethodCall:call
-                result:^(id _Nullable result) {
-                  XCTAssertEqual(result, FlutterMethodNotImplemented,
-                                 @"result block must be called with FlutterMethodNotImplemented");
-                  [resultExpectation fulfill];
-                }];
-
-  [self waitForExpectationsWithTimeout:1 handler:nil];
-}
-
-- (void)testApplicationPerformActionForShortcutItem {
-  id mockChannel = OCMClassMock([FlutterMethodChannel class]);
-  QuickActionsPlugin *plugin =
-      [[QuickActionsPlugin alloc] initWithChannel:mockChannel
-                             shortcutStateManager:OCMClassMock([FLTShortcutStateManager class])];
-
-  UIApplicationShortcutItem *item = [[UIApplicationShortcutItem alloc]
-           initWithType:@"SearchTheThing"
-         localizedTitle:@"Search the thing"
-      localizedSubtitle:nil
-                   icon:[UIApplicationShortcutIcon
-                            iconWithTemplateImageName:@"search_the_thing.png"]
-               userInfo:nil];
-
-  BOOL actionResult = [plugin application:[UIApplication sharedApplication]
-             performActionForShortcutItem:item
-                        completionHandler:^(BOOL succeeded){/* no-op */}];
-  XCTAssert(actionResult, @"performActionForShortcutItem must return true.");
-  OCMVerify([mockChannel invokeMethod:@"launch" arguments:item.type]);
-}
-
-- (void)testApplicationDidFinishLaunchingWithOptions_launchWithShortcut {
-  id mockShortcutStateManager = OCMClassMock([FLTShortcutStateManager class]);
-  QuickActionsPlugin *plugin =
-      [[QuickActionsPlugin alloc] initWithChannel:OCMClassMock([FlutterMethodChannel class])
-                             shortcutStateManager:mockShortcutStateManager];
-
-  UIApplicationShortcutItem *item = [[UIApplicationShortcutItem alloc]
-           initWithType:@"SearchTheThing"
-         localizedTitle:@"Search the thing"
-      localizedSubtitle:nil
-                   icon:[UIApplicationShortcutIcon
-                            iconWithTemplateImageName:@"search_the_thing.png"]
-               userInfo:nil];
-
-  BOOL launchResult = [plugin application:[UIApplication sharedApplication]
-            didFinishLaunchingWithOptions:@{UIApplicationLaunchOptionsShortcutItemKey : item}];
-
-  XCTAssertFalse(launchResult,
-                 @"didFinishLaunchingWithOptions must return false if launched from shortcut.");
-}
-
-- (void)testApplicationDidFinishLaunchingWithOptions_launchWithoutShortcut {
-  QuickActionsPlugin *plugin =
-      [[QuickActionsPlugin alloc] initWithChannel:OCMClassMock([FlutterMethodChannel class])
-                             shortcutStateManager:OCMClassMock([FLTShortcutStateManager class])];
-  BOOL launchResult = [plugin application:[UIApplication sharedApplication]
-            didFinishLaunchingWithOptions:@{}];
-  XCTAssertTrue(launchResult,
-                @"didFinishLaunchingWithOptions must return true if not launched from shortcut.");
-}
-
-- (void)testApplicationDidBecomeActive_launchWithoutShortcut {
-  id mockChannel = OCMClassMock([FlutterMethodChannel class]);
-  id mockShortcutStateManager = OCMClassMock([FLTShortcutStateManager class]);
-  QuickActionsPlugin *plugin =
-      [[QuickActionsPlugin alloc] initWithChannel:mockChannel
-                             shortcutStateManager:mockShortcutStateManager];
-
-  BOOL launchResult = [plugin application:[UIApplication sharedApplication]
-            didFinishLaunchingWithOptions:@{}];
-  XCTAssertTrue(launchResult,
-                @"didFinishLaunchingWithOptions must return true if not launched from shortcut.");
-  [plugin applicationDidBecomeActive:[UIApplication sharedApplication]];
-  OCMVerify(never(), [mockChannel invokeMethod:OCMOCK_ANY arguments:OCMOCK_ANY]);
-}
-
-- (void)testApplicationDidBecomeActive_launchWithShortcut {
-  id mockChannel = OCMClassMock([FlutterMethodChannel class]);
-  id mockShortcutStateManager = OCMClassMock([FLTShortcutStateManager class]);
-  QuickActionsPlugin *plugin =
-      [[QuickActionsPlugin alloc] initWithChannel:mockChannel
-                             shortcutStateManager:mockShortcutStateManager];
-
-  UIApplicationShortcutItem *item = [[UIApplicationShortcutItem alloc]
-           initWithType:@"SearchTheThing"
-         localizedTitle:@"Search the thing"
-      localizedSubtitle:nil
-                   icon:[UIApplicationShortcutIcon
-                            iconWithTemplateImageName:@"search_the_thing.png"]
-               userInfo:nil];
-  BOOL launchResult = [plugin application:[UIApplication sharedApplication]
-            didFinishLaunchingWithOptions:@{UIApplicationLaunchOptionsShortcutItemKey : item}];
-  XCTAssertFalse(launchResult,
-                 @"didFinishLaunchingWithOptions must return false if launched from shortcut.");
-  [plugin applicationDidBecomeActive:[UIApplication sharedApplication]];
-  OCMVerify([mockChannel invokeMethod:@"launch" arguments:item.type]);
-}
-
-- (void)testApplicationDidBecomeActive_launchWithShortcut_becomeActiveTwice {
-  id mockChannel = OCMClassMock([FlutterMethodChannel class]);
-  id mockShortcutStateManager = OCMClassMock([FLTShortcutStateManager class]);
-  QuickActionsPlugin *plugin =
-      [[QuickActionsPlugin alloc] initWithChannel:mockChannel
-                             shortcutStateManager:mockShortcutStateManager];
-
-  UIApplicationShortcutItem *item = [[UIApplicationShortcutItem alloc]
-           initWithType:@"SearchTheThing"
-         localizedTitle:@"Search the thing"
-      localizedSubtitle:nil
-                   icon:[UIApplicationShortcutIcon
-                            iconWithTemplateImageName:@"search_the_thing.png"]
-               userInfo:nil];
-  BOOL launchResult = [plugin application:[UIApplication sharedApplication]
-            didFinishLaunchingWithOptions:@{UIApplicationLaunchOptionsShortcutItemKey : item}];
-  XCTAssertFalse(launchResult,
-                 @"didFinishLaunchingWithOptions must return false if launched from shortcut.");
-  [plugin applicationDidBecomeActive:[UIApplication sharedApplication]];
-  [plugin applicationDidBecomeActive:[UIApplication sharedApplication]];
-  // shortcut should only be handled once per launch.
-  OCMVerify(times(1), [mockChannel invokeMethod:@"launch" arguments:item.type]);
-}
-
-@end
diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/FLTShortcutStateManagerTests.m b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/FLTShortcutStateManagerTests.m
deleted file mode 100644
index 96fbf22..0000000
--- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/FLTShortcutStateManagerTests.m
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-@import quick_actions_ios;
-@import XCTest;
-#import <OCMock/OCMock.h>
-
-@interface FLTShortcutStateManagerTests : XCTestCase
-@end
-
-@implementation FLTShortcutStateManagerTests
-
-- (void)testSetShortcutItems_shouldSetItem {
-  id mockApplication = OCMPartialMock([UIApplication sharedApplication]);
-  OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
-
-  FLTShortcutStateManager *shortcutStateManager = [[FLTShortcutStateManager alloc] init];
-
-  NSDictionary *rawItem = @{
-    @"type" : @"SearchTheThing",
-    @"localizedTitle" : @"Search the thing",
-    @"icon" : @"search_the_thing.png",
-  };
-
-  [shortcutStateManager setShortcutItems:@[ rawItem ]];
-
-  UIApplicationShortcutItem *expectedItem = [[UIApplicationShortcutItem alloc]
-           initWithType:@"SearchTheThing"
-         localizedTitle:@"Search the thing"
-      localizedSubtitle:nil
-                   icon:[UIApplicationShortcutIcon
-                            iconWithTemplateImageName:@"search_the_thing.png"]
-               userInfo:nil];
-
-  OCMVerify([mockApplication setShortcutItems:@[ expectedItem ]]);
-}
-
-- (void)testSetShortcutItems_shouldSetItemWithoutIcon {
-  id mockApplication = OCMPartialMock([UIApplication sharedApplication]);
-  OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
-
-  NSDictionary *rawItem = @{
-    @"type" : @"SearchTheThing",
-    @"localizedTitle" : @"Search the thing",
-    // Dart's null value is passed to iOS as `NSNull`.
-    // The key value pair is still present in the dictionary.
-    @"icon" : [NSNull null],
-  };
-  FLTShortcutStateManager *shortcutStateManager = [[FLTShortcutStateManager alloc] init];
-  [shortcutStateManager setShortcutItems:@[ rawItem ]];
-
-  UIApplicationShortcutItem *expectedItem =
-      [[UIApplicationShortcutItem alloc] initWithType:@"SearchTheThing"
-                                       localizedTitle:@"Search the thing"
-                                    localizedSubtitle:nil
-                                                 icon:nil
-                                             userInfo:nil];
-  OCMVerify([mockApplication setShortcutItems:@[ expectedItem ]]);
-}
-
-@end
diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockMethodChannel.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockMethodChannel.swift
new file mode 100644
index 0000000..b52fa1d
--- /dev/null
+++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockMethodChannel.swift
@@ -0,0 +1,14 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import Foundation
+
+@testable import quick_actions_ios
+
+final class MockMethodChannel: MethodChannel {
+  var invokeMethodStub: ((_ methods: String, _ arguments: Any?) -> Void)? = nil
+  func invokeMethod(_ method: String, arguments: Any?) {
+    invokeMethodStub?(method, arguments)
+  }
+}
diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemParser.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemParser.swift
new file mode 100644
index 0000000..3b5a096
--- /dev/null
+++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemParser.swift
@@ -0,0 +1,16 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import Foundation
+
+@testable import quick_actions_ios
+
+final class MockShortcutItemParser: ShortcutItemParser {
+
+  var parseShortcutItemsStub: ((_ items: [[String: Any]]) -> [UIApplicationShortcutItem])? = nil
+
+  func parseShortcutItems(_ items: [[String: Any]]) -> [UIApplicationShortcutItem] {
+    return parseShortcutItemsStub?(items) ?? []
+  }
+}
diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemProvider.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemProvider.swift
new file mode 100644
index 0000000..8547741
--- /dev/null
+++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemProvider.swift
@@ -0,0 +1,9 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+@testable import quick_actions_ios
+
+final class MockShortcutItemProvider: ShortcutItemProviding {
+  var shortcutItems: [UIApplicationShortcutItem]? = nil
+}
diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift
new file mode 100644
index 0000000..268a89b
--- /dev/null
+++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift
@@ -0,0 +1,294 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import Flutter
+import XCTest
+
+@testable import quick_actions_ios
+
+class QuickActionsPluginTests: XCTestCase {
+
+  func testHandleMethodCall_setShortcutItems() {
+    let rawItem = [
+      "type": "SearchTheThing",
+      "localizedTitle": "Search the thing",
+      "icon": "search_the_thing.png",
+    ]
+    let item = UIApplicationShortcutItem(
+      type: "SearchTheThing",
+      localizedTitle: "Search the thing",
+      localizedSubtitle: nil,
+      icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"),
+      userInfo: nil)
+
+    let call = FlutterMethodCall(methodName: "setShortcutItems", arguments: [rawItem])
+
+    let mockChannel = MockMethodChannel()
+    let mockShortcutItemProvider = MockShortcutItemProvider()
+    let mockShortcutItemParser = MockShortcutItemParser()
+
+    let plugin = QuickActionsPlugin(
+      channel: mockChannel,
+      shortcutItemProvider: mockShortcutItemProvider,
+      shortcutItemParser: mockShortcutItemParser)
+
+    let parseShortcutItemsExpectation = expectation(
+      description: "parseShortcutItems must be called.")
+    mockShortcutItemParser.parseShortcutItemsStub = { items in
+      XCTAssertEqual(items as? [[String: String]], [rawItem])
+      parseShortcutItemsExpectation.fulfill()
+      return [item]
+    }
+
+    let resultExpectation = expectation(description: "result block must be called.")
+    plugin.handle(call) { result in
+      XCTAssertNil(result, "result block must be called with nil.")
+      resultExpectation.fulfill()
+    }
+    XCTAssertEqual(mockShortcutItemProvider.shortcutItems, [item], "Must set shortcut items.")
+    waitForExpectations(timeout: 1)
+  }
+
+  func testHandleMethodCall_clearShortcutItems() {
+    let item = UIApplicationShortcutItem(
+      type: "SearchTheThing",
+      localizedTitle: "Search the thing",
+      localizedSubtitle: nil,
+      icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"),
+      userInfo: nil)
+
+    let call = FlutterMethodCall(methodName: "clearShortcutItems", arguments: nil)
+    let mockChannel = MockMethodChannel()
+    let mockShortcutItemProvider = MockShortcutItemProvider()
+    let mockShortcutItemParser = MockShortcutItemParser()
+
+    mockShortcutItemProvider.shortcutItems = [item]
+
+    let plugin = QuickActionsPlugin(
+      channel: mockChannel,
+      shortcutItemProvider: mockShortcutItemProvider,
+      shortcutItemParser: mockShortcutItemParser)
+
+    let resultExpectation = expectation(description: "result block must be called.")
+    plugin.handle(call) { result in
+      XCTAssertNil(result, "result block must be called with nil.")
+      resultExpectation.fulfill()
+    }
+
+    XCTAssertEqual(mockShortcutItemProvider.shortcutItems, [], "Must clear shortcut items.")
+    waitForExpectations(timeout: 1)
+  }
+
+  func testHandleMethodCall_getLaunchAction() {
+    let call = FlutterMethodCall(methodName: "getLaunchAction", arguments: nil)
+
+    let mockChannel = MockMethodChannel()
+    let mockShortcutItemProvider = MockShortcutItemProvider()
+    let mockShortcutItemParser = MockShortcutItemParser()
+
+    let plugin = QuickActionsPlugin(
+      channel: mockChannel,
+      shortcutItemProvider: mockShortcutItemProvider,
+      shortcutItemParser: mockShortcutItemParser)
+
+    let resultExpectation = expectation(description: "result block must be called.")
+    plugin.handle(call) { result in
+      XCTAssertNil(result, "result block must be called with nil.")
+      resultExpectation.fulfill()
+    }
+
+    waitForExpectations(timeout: 1)
+  }
+
+  func testHandleMethodCall_nonExistMethods() {
+    let call = FlutterMethodCall(methodName: "nonExist", arguments: nil)
+
+    let mockChannel = MockMethodChannel()
+    let mockShortcutItemProvider = MockShortcutItemProvider()
+    let mockShortcutItemParser = MockShortcutItemParser()
+
+    let plugin = QuickActionsPlugin(
+      channel: mockChannel,
+      shortcutItemProvider: mockShortcutItemProvider,
+      shortcutItemParser: mockShortcutItemParser)
+
+    let resultExpectation = expectation(description: "result block must be called.")
+
+    plugin.handle(call) { result in
+      XCTAssertEqual(
+        result as? NSObject, FlutterMethodNotImplemented,
+        "result block must be called with FlutterMethodNotImplemented")
+      resultExpectation.fulfill()
+    }
+
+    waitForExpectations(timeout: 1)
+  }
+
+  func testApplicationPerformActionForShortcutItem() {
+    let mockChannel = MockMethodChannel()
+    let mockShortcutItemProvider = MockShortcutItemProvider()
+    let mockShortcutItemParser = MockShortcutItemParser()
+
+    let plugin = QuickActionsPlugin(
+      channel: mockChannel,
+      shortcutItemProvider: mockShortcutItemProvider,
+      shortcutItemParser: mockShortcutItemParser)
+
+    let item = UIApplicationShortcutItem(
+      type: "SearchTheThing",
+      localizedTitle: "Search the thing",
+      localizedSubtitle: nil,
+      icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"),
+      userInfo: nil)
+
+    let invokeMethodExpectation = expectation(description: "invokeMethod must be called.")
+    mockChannel.invokeMethodStub = { method, arguments in
+      XCTAssertEqual(method, "launch")
+      XCTAssertEqual(arguments as? String, item.type)
+      invokeMethodExpectation.fulfill()
+    }
+
+    let actionResult = plugin.application(
+      UIApplication.shared,
+      performActionFor: item
+    ) { success in /* no-op */ }
+
+    XCTAssert(actionResult, "performActionForShortcutItem must return true.")
+    waitForExpectations(timeout: 1)
+  }
+
+  func testApplicationDidFinishLaunchingWithOptions_launchWithShortcut() {
+    let mockChannel = MockMethodChannel()
+    let mockShortcutItemProvider = MockShortcutItemProvider()
+    let mockShortcutItemParser = MockShortcutItemParser()
+
+    let plugin = QuickActionsPlugin(
+      channel: mockChannel,
+      shortcutItemProvider: mockShortcutItemProvider,
+      shortcutItemParser: mockShortcutItemParser)
+
+    let item = UIApplicationShortcutItem(
+      type: "SearchTheThing",
+      localizedTitle: "Search the thing",
+      localizedSubtitle: nil,
+      icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"),
+      userInfo: nil)
+
+    let launchResult = plugin.application(
+      UIApplication.shared,
+      didFinishLaunchingWithOptions: [UIApplication.LaunchOptionsKey.shortcutItem: item])
+    XCTAssertFalse(
+      launchResult, "didFinishLaunchingWithOptions must return false if launched from shortcut.")
+  }
+
+  func testApplicationDidFinishLaunchingWithOptions_launchWithoutShortcut() {
+    let mockChannel = MockMethodChannel()
+    let mockShortcutItemProvider = MockShortcutItemProvider()
+    let mockShortcutItemParser = MockShortcutItemParser()
+
+    let plugin = QuickActionsPlugin(
+      channel: mockChannel,
+      shortcutItemProvider: mockShortcutItemProvider,
+      shortcutItemParser: mockShortcutItemParser)
+
+    let launchResult = plugin.application(UIApplication.shared, didFinishLaunchingWithOptions: [:])
+    XCTAssert(
+      launchResult, "didFinishLaunchingWithOptions must return true if not launched from shortcut.")
+  }
+
+  func testApplicationDidBecomeActive_launchWithoutShortcut() {
+    let mockChannel = MockMethodChannel()
+    let mockShortcutItemProvider = MockShortcutItemProvider()
+    let mockShortcutItemParser = MockShortcutItemParser()
+
+    let plugin = QuickActionsPlugin(
+      channel: mockChannel,
+      shortcutItemProvider: mockShortcutItemProvider,
+      shortcutItemParser: mockShortcutItemParser)
+
+    mockChannel.invokeMethodStub = { _, _ in
+      XCTFail("invokeMethod should not be called if launch without shortcut.")
+    }
+
+    let launchResult = plugin.application(UIApplication.shared, didFinishLaunchingWithOptions: [:])
+    XCTAssert(
+      launchResult, "didFinishLaunchingWithOptions must return true if not launched from shortcut.")
+
+    plugin.applicationDidBecomeActive(UIApplication.shared)
+  }
+
+  func testApplicationDidBecomeActive_launchWithShortcut() {
+    let item = UIApplicationShortcutItem(
+      type: "SearchTheThing",
+      localizedTitle: "Search the thing",
+      localizedSubtitle: nil,
+      icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"),
+      userInfo: nil)
+
+    let mockChannel = MockMethodChannel()
+    let mockShortcutItemProvider = MockShortcutItemProvider()
+    let mockShortcutItemParser = MockShortcutItemParser()
+
+    let plugin = QuickActionsPlugin(
+      channel: mockChannel,
+      shortcutItemProvider: mockShortcutItemProvider,
+      shortcutItemParser: mockShortcutItemParser)
+
+    let invokeMethodExpectation = expectation(description: "invokeMethod must be called.")
+    mockChannel.invokeMethodStub = { method, arguments in
+      XCTAssertEqual(method, "launch")
+      XCTAssertEqual(arguments as? String, item.type)
+      invokeMethodExpectation.fulfill()
+    }
+
+    let launchResult = plugin.application(
+      UIApplication.shared,
+      didFinishLaunchingWithOptions: [UIApplication.LaunchOptionsKey.shortcutItem: item])
+
+    XCTAssertFalse(
+      launchResult, "didFinishLaunchingWithOptions must return false if launched from shortcut.")
+
+    plugin.applicationDidBecomeActive(UIApplication.shared)
+    waitForExpectations(timeout: 1)
+  }
+
+  func testApplicationDidBecomeActive_launchWithShortcut_becomeActiveTwice() {
+    let item = UIApplicationShortcutItem(
+      type: "SearchTheThing",
+      localizedTitle: "Search the thing",
+      localizedSubtitle: nil,
+      icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"),
+      userInfo: nil)
+
+    let mockChannel = MockMethodChannel()
+    let mockShortcutItemProvider = MockShortcutItemProvider()
+    let mockShortcutItemParser = MockShortcutItemParser()
+
+    let plugin = QuickActionsPlugin(
+      channel: mockChannel,
+      shortcutItemProvider: mockShortcutItemProvider,
+      shortcutItemParser: mockShortcutItemParser)
+
+    let invokeMethodExpectation = expectation(description: "invokeMethod must be called.")
+
+    var invokeMehtodCount = 0
+    mockChannel.invokeMethodStub = { method, arguments in
+      invokeMehtodCount += 1
+      invokeMethodExpectation.fulfill()
+    }
+
+    let launchResult = plugin.application(
+      UIApplication.shared,
+      didFinishLaunchingWithOptions: [UIApplication.LaunchOptionsKey.shortcutItem: item])
+
+    XCTAssertFalse(
+      launchResult, "didFinishLaunchingWithOptions must return false if launched from shortcut.")
+
+    plugin.applicationDidBecomeActive(UIApplication.shared)
+    waitForExpectations(timeout: 1)
+
+    XCTAssertEqual(invokeMehtodCount, 1, "shortcut should only be handled once per launch.")
+  }
+
+}
diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/FLTShortcutStateManager.h b/packages/quick_actions/quick_actions_ios/ios/Classes/FLTShortcutStateManager.h
deleted file mode 100644
index 05d0433..0000000
--- a/packages/quick_actions/quick_actions_ios/ios/Classes/FLTShortcutStateManager.h
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import <Foundation/Foundation.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-/// Manages the shortcut related states.
-@interface FLTShortcutStateManager : NSObject
-
-/// Sets the list of shortcut items.
-///
-/// @param items the list of shortcut items to be parsed and set.
-- (void)setShortcutItems:(NSArray *)items API_AVAILABLE(ios(9.0));
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/FLTShortcutStateManager.m b/packages/quick_actions/quick_actions_ios/ios/Classes/FLTShortcutStateManager.m
deleted file mode 100644
index e39edd2..0000000
--- a/packages/quick_actions/quick_actions_ios/ios/Classes/FLTShortcutStateManager.m
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "FLTShortcutStateManager.h"
-
-@implementation FLTShortcutStateManager
-
-- (void)setShortcutItems:(NSArray *)items {
-  NSMutableArray<UIApplicationShortcutItem *> *newShortcuts = [[NSMutableArray alloc] init];
-
-  for (id item in items) {
-    UIApplicationShortcutItem *shortcut = [self deserializeShortcutItem:item];
-    [newShortcuts addObject:shortcut];
-  }
-
-  [UIApplication sharedApplication].shortcutItems = newShortcuts;
-}
-
-- (UIApplicationShortcutItem *)deserializeShortcutItem:(NSDictionary *)serialized {
-  UIApplicationShortcutIcon *icon =
-      [serialized[@"icon"] isKindOfClass:[NSNull class]]
-          ? nil
-          : [UIApplicationShortcutIcon iconWithTemplateImageName:serialized[@"icon"]];
-  return [[UIApplicationShortcutItem alloc] initWithType:serialized[@"type"]
-                                          localizedTitle:serialized[@"localizedTitle"]
-                                       localizedSubtitle:nil
-                                                    icon:icon
-                                                userInfo:nil];
-}
-
-@end
diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/MethodChannel.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/MethodChannel.swift
new file mode 100644
index 0000000..5d52790
--- /dev/null
+++ b/packages/quick_actions/quick_actions_ios/ios/Classes/MethodChannel.swift
@@ -0,0 +1,16 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import Flutter
+
+/// A channel for platform code to communicate with the Dart code.
+protocol MethodChannel {
+  /// Invokes a method in Dart code.
+  /// - Parameter method the method name.
+  /// - Parameter arguments the method arguments.
+  func invokeMethod(_ method: String, arguments: Any?)
+}
+
+/// A default implementation of the `MethodChannel` protocol.
+extension FlutterMethodChannel: MethodChannel {}
diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift
index 26d6d20..8522c5f 100644
--- a/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift
+++ b/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift
@@ -15,19 +15,20 @@
     registrar.addApplicationDelegate(instance)
   }
 
-  private let channel: FlutterMethodChannel
-  private let shortcutStateManager: FLTShortcutStateManager
+  private let channel: MethodChannel
+  private let shortcutItemProvider: ShortcutItemProviding
+  private let shortcutItemParser: ShortcutItemParser
   /// The type of the shortcut item selected when launching the app.
   private var launchingShortcutType: String? = nil
 
-  // TODO: (hellohuanlin) remove `@objc` attribute and make it non-public after migrating tests to Swift.
-  @objc
-  public init(
-    channel: FlutterMethodChannel,
-    shortcutStateManager: FLTShortcutStateManager = FLTShortcutStateManager()
+  init(
+    channel: MethodChannel,
+    shortcutItemProvider: ShortcutItemProviding = UIApplication.shared,
+    shortcutItemParser: ShortcutItemParser = DefaultShortcutItemParser()
   ) {
     self.channel = channel
-    self.shortcutStateManager = shortcutStateManager
+    self.shortcutItemProvider = shortcutItemProvider
+    self.shortcutItemParser = shortcutItemParser
   }
 
   public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
@@ -35,10 +36,10 @@
     case "setShortcutItems":
       // `arguments` must be an array of dictionaries
       let items = call.arguments as! [[String: Any]]
-      shortcutStateManager.setShortcutItems(items)
+      shortcutItemProvider.shortcutItems = shortcutItemParser.parseShortcutItems(items)
       result(nil)
     case "clearShortcutItems":
-      shortcutStateManager.setShortcutItems([])
+      shortcutItemProvider.shortcutItems = []
       result(nil)
     case "getLaunchAction":
       result(nil)
diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemParser.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemParser.swift
new file mode 100644
index 0000000..0945b4a
--- /dev/null
+++ b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemParser.swift
@@ -0,0 +1,46 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import UIKit
+
+/// A parser that parses an array of raw shortcut items.
+protocol ShortcutItemParser {
+
+  /// Parses an array of raw shortcut items into an array of UIApplicationShortcutItems
+  ///
+  /// - Parameter items an array of raw shortcut items to be parsed.
+  /// - Returns an array of parsed shortcut items to be set.
+  ///
+  func parseShortcutItems(_ items: [[String: Any]]) -> [UIApplicationShortcutItem]
+}
+
+/// A default implementation of the `ShortcutItemParser` protocol.
+final class DefaultShortcutItemParser: ShortcutItemParser {
+
+  func parseShortcutItems(_ items: [[String: Any]]) -> [UIApplicationShortcutItem] {
+    return items.compactMap { deserializeShortcutItem(with: $0) }
+  }
+
+  private func deserializeShortcutItem(with serialized: [String: Any]) -> UIApplicationShortcutItem?
+  {
+    guard
+      let type = serialized["type"] as? String,
+      let localizedTitle = serialized["localizedTitle"] as? String
+    else {
+      return nil
+    }
+
+    let icon = (serialized["icon"] as? String).map {
+      UIApplicationShortcutIcon(templateImageName: $0)
+    }
+
+    // type and localizedTitle are required.
+    return UIApplicationShortcutItem(
+      type: type,
+      localizedTitle: localizedTitle,
+      localizedSubtitle: nil,
+      icon: icon,
+      userInfo: nil)
+  }
+}
diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemProviding.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemProviding.swift
new file mode 100644
index 0000000..e885486
--- /dev/null
+++ b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemProviding.swift
@@ -0,0 +1,15 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import UIKit
+
+/// Provides the capability to get and set the app's home screen shortcut items.
+protocol ShortcutItemProviding: AnyObject {
+
+  /// An array of shortcut items for home screen.
+  var shortcutItems: [UIApplicationShortcutItem]? { get set }
+}
+
+/// A default implementation of the `ShortcutItemProviding` protocol.
+extension UIApplication: ShortcutItemProviding {}
diff --git a/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios.podspec b/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios.podspec
index d8090ca..a6fff92 100644
--- a/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios.podspec
+++ b/packages/quick_actions/quick_actions_ios/ios/quick_actions_ios.podspec
@@ -15,12 +15,11 @@
   s.source           = { :http => 'https://github.com/flutter/plugins/tree/main/packages/quick_actions' }
   s.documentation_url = 'https://pub.dev/packages/quick_actions'
   s.swift_version = '5.0'
-  s.source_files = 'Classes/**/*.{h,m,swift}'
+  s.source_files = 'Classes/**/*.swift'
   s.xcconfig = {
      'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift',
      'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift',
   }
-  s.public_header_files = 'Classes/**/*.h'
   s.dependency 'Flutter'
   s.platform = :ios, '9.0'
   s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
diff --git a/packages/quick_actions/quick_actions_ios/pubspec.yaml b/packages/quick_actions/quick_actions_ios/pubspec.yaml
index f01ae4a..6e7fb43 100644
--- a/packages/quick_actions/quick_actions_ios/pubspec.yaml
+++ b/packages/quick_actions/quick_actions_ios/pubspec.yaml
@@ -2,7 +2,7 @@
 description: An implementation for the iOS platform of the Flutter `quick_actions` plugin.
 repository: https://github.com/flutter/plugins/tree/main/packages/quick_actions/quick_actions_ios
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22
-version: 1.0.1
+version: 1.0.2
 
 environment:
   sdk: ">=2.15.0 <3.0.0"