diff --git a/iosHyperskillApp/Podfile b/iosHyperskillApp/Podfile
index 6cdd987662..045cd22969 100644
--- a/iosHyperskillApp/Podfile
+++ b/iosHyperskillApp/Podfile
@@ -31,6 +31,7 @@ target "iosHyperskillApp" do
   pod "SVProgressHUD", "2.3.1"
   pod "SkeletonUI", "1.0.11"
   pod "lottie-ios", "4.4.3"
+  pod "BEMCheckBox", "1.4.1"
 
   pod "PanModal", :git => "https://github.com/ivan-magda/PanModal.git", :branch => "remove-presenting-appearance-transitions"
   pod "CombineSchedulers", :git => "https://github.com/ivan-magda/combine-schedulers.git", :branch => "main"
diff --git a/iosHyperskillApp/Podfile.lock b/iosHyperskillApp/Podfile.lock
index 7b7d71a7e5..9df6b57437 100644
--- a/iosHyperskillApp/Podfile.lock
+++ b/iosHyperskillApp/Podfile.lock
@@ -12,6 +12,7 @@ PODS:
     - AppsFlyerFramework/Main (= 6.14.2)
   - AppsFlyerFramework/Main (6.14.2)
   - Atributika (4.10.1)
+  - BEMCheckBox (1.4.1)
   - CocoaLumberjack (3.8.2):
     - CocoaLumberjack/Core (= 3.8.2)
   - CocoaLumberjack/Core (3.8.2)
@@ -116,6 +117,7 @@ DEPENDENCIES:
   - AmplitudeSwift (= 1.4.5)
   - AppsFlyerFramework (= 6.14.2)
   - Atributika (= 4.10.1)
+  - BEMCheckBox (= 1.4.1)
   - CombineSchedulers (from `https://github.com/ivan-magda/combine-schedulers.git`, branch `main`)
   - Firebase/CoreOnly (= 10.24.0)
   - Firebase/Messaging (= 10.24.0)
@@ -143,6 +145,7 @@ SPEC REPOS:
     - AppAuth
     - AppsFlyerFramework
     - Atributika
+    - BEMCheckBox
     - CocoaLumberjack
     - Firebase
     - FirebaseCore
@@ -212,6 +215,7 @@ SPEC CHECKSUMS:
   AppAuth: 501c04eda8a8d11f179dbe8637b7a91bb7e5d2fa
   AppsFlyerFramework: b3de9a49c6af8a8e38c44603e468b5e207f22466
   Atributika: 47e778507cfb3cd2c996278b0285221a62e97d71
+  BEMCheckBox: 5ba6e37ade3d3657b36caecc35c8b75c6c2b1a4e
   CocoaLumberjack: f8d89a516e7710fdb2e9b8f1560b16ec6040eef0
   CombineSchedulers: 80f670c732b4754eb011cd1147d9a08654b1c463
   Firebase: 91fefd38712feb9186ea8996af6cbdef41473442
@@ -245,6 +249,6 @@ SPEC CHECKSUMS:
   SVProgressHUD: 4837c74bdfe2e51e8821c397825996a8d7de6e22
   SwiftLint: c1de071d9d08c8aba837545f6254315bc900e211
 
-PODFILE CHECKSUM: 9392d71b37a5fe79e30faedcb4e232aa00f3bc67
+PODFILE CHECKSUM: 1177593fe82412f60dfb7e58578b56066fe42f4f
 
 COCOAPODS: 1.15.2
diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
index 87cfd53c8d..008e05b4fb 100644
--- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
+++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
@@ -33,6 +33,14 @@
 		2C05AC5F2A0ED9710039C7EF /* BadgeView+ConcreateTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C05AC5E2A0ED9710039C7EF /* BadgeView+ConcreateTypes.swift */; };
 		2C05AC622A0EDFC20039C7EF /* ProjectSelectionListGridCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C05AC612A0EDFC20039C7EF /* ProjectSelectionListGridCellView.swift */; };
 		2C05AC642A0EDFD80039C7EF /* ProjectSelectionListGridCellBadgesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C05AC632A0EDFD80039C7EF /* ProjectSelectionListGridCellBadgesView.swift */; };
+		2C065AA22C52347E00B820F5 /* StepQuizTableSelectColumnsColumnView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C065AA12C52347E00B820F5 /* StepQuizTableSelectColumnsColumnView.swift */; };
+		2C065AA42C523EE900B820F5 /* StepQuizTableSelectColumnsHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C065AA32C523EE900B820F5 /* StepQuizTableSelectColumnsHeaderView.swift */; };
+		2C065AA62C5240E900B820F5 /* StepQuizTableSelectColumnsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C065AA52C5240E900B820F5 /* StepQuizTableSelectColumnsView.swift */; };
+		2C065AAA2C5242AE00B820F5 /* StepQuizTableSelectColumnsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C065AA92C5242AE00B820F5 /* StepQuizTableSelectColumnsViewController.swift */; };
+		2C065AAC2C524BB800B820F5 /* UIKitScrollableStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C065AAB2C524BB800B820F5 /* UIKitScrollableStackView.swift */; };
+		2C065AAE2C524C7600B820F5 /* UIStackView+RemoveAllArrangedSubviews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C065AAD2C524C7600B820F5 /* UIStackView+RemoveAllArrangedSubviews.swift */; };
+		2C065AB02C524CDA00B820F5 /* UIStackViewExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C065AAF2C524CDA00B820F5 /* UIStackViewExtensionTests.swift */; };
+		2C065AB22C524E4100B820F5 /* UIKitTapProxyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C065AB12C524E4100B820F5 /* UIKitTapProxyView.swift */; };
 		2C069EB128F03782009A3DA1 /* AnalyticExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C069EB028F03782009A3DA1 /* AnalyticExtensions.swift */; };
 		2C078CE52AE26CB400D97E24 /* FillBlanksQuizTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C078CE42AE26CB400D97E24 /* FillBlanksQuizTitleView.swift */; };
 		2C078CE72AE26E2000D97E24 /* UIKitSeparatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C078CE62AE26E2000D97E24 /* UIKitSeparatorView.swift */; };
@@ -315,9 +323,6 @@
 		2C8E4F982848961C0011ADFA /* UIViewController+PresentPanModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8E4F972848961C0011ADFA /* UIViewController+PresentPanModal.swift */; };
 		2C8E4F9A284897360011ADFA /* PanModalSwiftUIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8E4F99284897360011ADFA /* PanModalSwiftUIViewController.swift */; };
 		2C8E4F9C2848A1550011ADFA /* PanModalViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8E4F9B2848A1550011ADFA /* PanModalViewModifier.swift */; };
-		2C8E4FA12848BF1F0011ADFA /* StepQuizTableSelectColumnsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8E4FA02848BF1F0011ADFA /* StepQuizTableSelectColumnsViewController.swift */; };
-		2C8E4FB12848C9050011ADFA /* StepQuizTableSelectColumnsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8E4FB02848C9050011ADFA /* StepQuizTableSelectColumnsView.swift */; };
-		2C8E4FB42848CB980011ADFA /* StepQuizTableSelectColumnsColumnView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8E4FB32848CB980011ADFA /* StepQuizTableSelectColumnsColumnView.swift */; };
 		2C8E4FB628490C020011ADFA /* PanModalPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8E4FB528490C020011ADFA /* PanModalPresenter.swift */; };
 		2C8E66D52878771B00D3928D /* ProfilePresentationDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8E66D42878771B00D3928D /* ProfilePresentationDescription.swift */; };
 		2C8E66D7287877F500D3928D /* ProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8E66D6287877F500D3928D /* ProfileViewModel.swift */; };
@@ -815,6 +820,14 @@
 		2C05AC5E2A0ED9710039C7EF /* BadgeView+ConcreateTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BadgeView+ConcreateTypes.swift"; sourceTree = "<group>"; };
 		2C05AC612A0EDFC20039C7EF /* ProjectSelectionListGridCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectSelectionListGridCellView.swift; sourceTree = "<group>"; };
 		2C05AC632A0EDFD80039C7EF /* ProjectSelectionListGridCellBadgesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectSelectionListGridCellBadgesView.swift; sourceTree = "<group>"; };
+		2C065AA12C52347E00B820F5 /* StepQuizTableSelectColumnsColumnView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizTableSelectColumnsColumnView.swift; sourceTree = "<group>"; };
+		2C065AA32C523EE900B820F5 /* StepQuizTableSelectColumnsHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizTableSelectColumnsHeaderView.swift; sourceTree = "<group>"; };
+		2C065AA52C5240E900B820F5 /* StepQuizTableSelectColumnsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizTableSelectColumnsView.swift; sourceTree = "<group>"; };
+		2C065AA92C5242AE00B820F5 /* StepQuizTableSelectColumnsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizTableSelectColumnsViewController.swift; sourceTree = "<group>"; };
+		2C065AAB2C524BB800B820F5 /* UIKitScrollableStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitScrollableStackView.swift; sourceTree = "<group>"; };
+		2C065AAD2C524C7600B820F5 /* UIStackView+RemoveAllArrangedSubviews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIStackView+RemoveAllArrangedSubviews.swift"; sourceTree = "<group>"; };
+		2C065AAF2C524CDA00B820F5 /* UIStackViewExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIStackViewExtensionTests.swift; sourceTree = "<group>"; };
+		2C065AB12C524E4100B820F5 /* UIKitTapProxyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitTapProxyView.swift; sourceTree = "<group>"; };
 		2C069EB028F03782009A3DA1 /* AnalyticExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticExtensions.swift; sourceTree = "<group>"; };
 		2C078CE42AE26CB400D97E24 /* FillBlanksQuizTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FillBlanksQuizTitleView.swift; sourceTree = "<group>"; };
 		2C078CE62AE26E2000D97E24 /* UIKitSeparatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitSeparatorView.swift; sourceTree = "<group>"; };
@@ -1105,9 +1118,6 @@
 		2C8E4F972848961C0011ADFA /* UIViewController+PresentPanModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+PresentPanModal.swift"; sourceTree = "<group>"; };
 		2C8E4F99284897360011ADFA /* PanModalSwiftUIViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PanModalSwiftUIViewController.swift; sourceTree = "<group>"; };
 		2C8E4F9B2848A1550011ADFA /* PanModalViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PanModalViewModifier.swift; sourceTree = "<group>"; };
-		2C8E4FA02848BF1F0011ADFA /* StepQuizTableSelectColumnsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizTableSelectColumnsViewController.swift; sourceTree = "<group>"; };
-		2C8E4FB02848C9050011ADFA /* StepQuizTableSelectColumnsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizTableSelectColumnsView.swift; sourceTree = "<group>"; };
-		2C8E4FB32848CB980011ADFA /* StepQuizTableSelectColumnsColumnView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizTableSelectColumnsColumnView.swift; sourceTree = "<group>"; };
 		2C8E4FB528490C020011ADFA /* PanModalPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PanModalPresenter.swift; sourceTree = "<group>"; };
 		2C8E66D42878771B00D3928D /* ProfilePresentationDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePresentationDescription.swift; sourceTree = "<group>"; };
 		2C8E66D6287877F500D3928D /* ProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewModel.swift; sourceTree = "<group>"; };
@@ -1956,6 +1966,7 @@
 		2C1F587C280D2F6600372A37 /* ExtensionsTests */ = {
 			isa = PBXGroup;
 			children = (
+				2C065AAF2C524CDA00B820F5 /* UIStackViewExtensionTests.swift */,
 				2C1F587D280D2F9200372A37 /* URLExtensionsTests.swift */,
 			);
 			path = ExtensionsTests;
@@ -2913,21 +2924,14 @@
 		2C8E4F9D2848BE420011ADFA /* StepQuizTableSelectColumns */ = {
 			isa = PBXGroup;
 			children = (
-				2C8E4FA02848BF1F0011ADFA /* StepQuizTableSelectColumnsViewController.swift */,
-				2C8E4FB22848CB6A0011ADFA /* Views */,
+				2C065AA12C52347E00B820F5 /* StepQuizTableSelectColumnsColumnView.swift */,
+				2C065AA32C523EE900B820F5 /* StepQuizTableSelectColumnsHeaderView.swift */,
+				2C065AA52C5240E900B820F5 /* StepQuizTableSelectColumnsView.swift */,
+				2C065AA92C5242AE00B820F5 /* StepQuizTableSelectColumnsViewController.swift */,
 			);
 			path = StepQuizTableSelectColumns;
 			sourceTree = "<group>";
 		};
-		2C8E4FB22848CB6A0011ADFA /* Views */ = {
-			isa = PBXGroup;
-			children = (
-				2C8E4FB32848CB980011ADFA /* StepQuizTableSelectColumnsColumnView.swift */,
-				2C8E4FB02848C9050011ADFA /* StepQuizTableSelectColumnsView.swift */,
-			);
-			path = Views;
-			sourceTree = "<group>";
-		};
 		2C8EBE522A4D475200A77205 /* Project */ = {
 			isa = PBXGroup;
 			children = (
@@ -3195,6 +3199,7 @@
 				2C2CCB482B74FA6600D1E596 /* UIFont+PreferredFont.swift */,
 				2C7CB6812ADFDB45006F78DA /* UIFont+SizeOfString.swift */,
 				2CDF14D728EF1E080060D972 /* UINavigationControllerExtensions.swift */,
+				2C065AAD2C524C7600B820F5 /* UIStackView+RemoveAllArrangedSubviews.swift */,
 				2C5B2A22286596400097B270 /* UITableView+RegisterReusable.swift */,
 				2CC78D0D28C75A3D0006EF91 /* UIViewControllerExtensions.swift */,
 				2CA7B89328932EB100A789EF /* UIWindowExtensions.swift */,
@@ -3516,7 +3521,9 @@
 				2CBD1918291D399500F5FB0B /* UIKitBounceButton.swift */,
 				2CE1E188292CCB450041FE14 /* UIKitIntrospectionView.swift */,
 				2CBD191C291D3BF400F5FB0B /* UIKitRoundedRectangleButton.swift */,
+				2C065AAB2C524BB800B820F5 /* UIKitScrollableStackView.swift */,
 				2C078CE62AE26E2000D97E24 /* UIKitSeparatorView.swift */,
+				2C065AB12C524E4100B820F5 /* UIKitTapProxyView.swift */,
 				2C5F19162AE6857F0039414D /* CollectionViewLayouts */,
 			);
 			path = UIKit;
@@ -4908,6 +4915,7 @@
 				2CB2BDB12BEB7F65009E2D83 /* CodePlaygroundGetChangesSubstringTests.swift in Sources */,
 				2CB2BDB52BEB81A1009E2D83 /* CodePlaygroundShouldMakeTabLineAfterTests.swift in Sources */,
 				2C919E3727EF00950022A2F2 /* QueueTests.swift in Sources */,
+				2C065AB02C524CDA00B820F5 /* UIStackViewExtensionTests.swift in Sources */,
 				2CB2BDB92BEB934D009E2D83 /* CodePlaygroundShouldMakeTabLineAfterPerformanceTests.swift in Sources */,
 				2CB2BDB72BEB8488009E2D83 /* CodePlaygroundGetChangesSubstringPerformanceTests.swift in Sources */,
 				2CF87DA929B71D500092FF83 /* IntrospectViewControllerTests.swift in Sources */,
@@ -4936,6 +4944,7 @@
 				E9F27D782906447A007F16D7 /* StepQuizHintCardView.swift in Sources */,
 				E9F0A2AA29D417C800C4A61E /* StudyPlanSectionHeaderView.swift in Sources */,
 				2C8EBE542A4D476700A77205 /* ProgressScreenProjectProgressContentView.swift in Sources */,
+				2C065AAA2C5242AE00B820F5 /* StepQuizTableSelectColumnsViewController.swift in Sources */,
 				E9FB89A72893CD2C0011EFFB /* LocalNotificationsService.swift in Sources */,
 				E9CF48A92A38499F00938CCE /* StreakRecoveryModalViewController.swift in Sources */,
 				2CC78D0928C74E7D0006EF91 /* UIViewControllerEventsWrapper.swift in Sources */,
@@ -5009,6 +5018,7 @@
 				2C725B612809125700A49043 /* LayoutInsets.swift in Sources */,
 				2CEB50CE288AACEA0044F9AB /* StepQuizCodeFullScreenTab.swift in Sources */,
 				2CEB50C8288A94050044F9AB /* BlockExtensions.swift in Sources */,
+				2C065AAE2C524C7600B820F5 /* UIStackView+RemoveAllArrangedSubviews.swift in Sources */,
 				2C54E4282A1F717F003406B9 /* CardView.swift in Sources */,
 				2C5837A62B284E570096B89B /* NavigationLink+Empty.swift in Sources */,
 				E9D2D675284E0B30000757AC /* StepQuizMatchingView.swift in Sources */,
@@ -5087,7 +5097,6 @@
 				2CD20ED12B73475400FB5269 /* ApplicationShortcutsService.swift in Sources */,
 				2C5F4A5A2971C71200677530 /* GamificationToolbarContent.swift in Sources */,
 				2C5B2A1F286595AF0097B270 /* CodeCompletionTableViewController.swift in Sources */,
-				2C8E4FB12848C9050011ADFA /* StepQuizTableSelectColumnsView.swift in Sources */,
 				2CE0F6EE2BB40B760032C439 /* StepFeedbackViewModel.swift in Sources */,
 				2CCCA3A12862E62F00D98089 /* StepQuizStringViewData.swift in Sources */,
 				2C1061A2285C349400EBD614 /* StepQuizChildQuizAssembly.swift in Sources */,
@@ -5134,7 +5143,6 @@
 				2C8247AF2BB6BAAF00CDD668 /* StepQuizCodeFixCodeMistakesBadge.swift in Sources */,
 				2CEB50D0288AADA40044F9AB /* StepQuizCodeFullScreenDetailsView.swift in Sources */,
 				E9A1DA702ACFF86B006A9D4B /* FirstProblemOnboardingFeatureViewStateKsExtensions.swift in Sources */,
-				2C8E4FB42848CB980011ADFA /* StepQuizTableSelectColumnsColumnView.swift in Sources */,
 				2C83FBC22B1781FA007AD7E2 /* LeaderboardListRowView.swift in Sources */,
 				2CEB33772949930B00B9E437 /* StepQuizSQLSkeletonView.swift in Sources */,
 				2CB45762288EC29D007C2D77 /* StepQuizActionButtons.swift in Sources */,
@@ -5168,6 +5176,7 @@
 				2C5B2A1D286595960097B270 /* CodeCompletionTableViewCell.swift in Sources */,
 				2C27C77E28773042006A641A /* NukeManager.swift in Sources */,
 				2C0DB91028645332001EA35E /* CodeEditorTheme.swift in Sources */,
+				2C065AA62C5240E900B820F5 /* StepQuizTableSelectColumnsView.swift in Sources */,
 				2CBD1919291D399500F5FB0B /* UIKitBounceButton.swift in Sources */,
 				2CAA3C6D2AA9CA9D004F6CE6 /* StepQuizProblemOnboardingModalViewController.swift in Sources */,
 				E9523BF029DA933C0013A661 /* StudyPlanViewModel.swift in Sources */,
@@ -5227,6 +5236,7 @@
 				E996D414292228A700A47498 /* TopicsRepetitionsView.swift in Sources */,
 				E94BB0462A9DEF7C00736B7C /* StepQuizParsonsControlsView.swift in Sources */,
 				2C5CBBE72948FC7A00113007 /* StepQuizSQLView.swift in Sources */,
+				2C065AAC2C524BB800B820F5 /* UIKitScrollableStackView.swift in Sources */,
 				2C54E4262A1F7086003406B9 /* TrackSelectionDetailsDescriptionView.swift in Sources */,
 				2CEA454F2BD6B75C0011838D /* StepQuizToolbarContent.swift in Sources */,
 				2C9AA4052C2556F400F5170E /* WelcomeOnboardingOutputProtocol.swift in Sources */,
@@ -5238,6 +5248,7 @@
 				2C7CB66B2ADFB947006F78DA /* StepQuizFillBlanksAssembly.swift in Sources */,
 				2CBC97CA2A5553330078E445 /* StageImplementStageCompletedModalViewController.swift in Sources */,
 				2C20FBC9284F6F97006D879E /* UnitConverters.swift in Sources */,
+				2C065AB22C524E4100B820F5 /* UIKitTapProxyView.swift in Sources */,
 				2C336D152865C49D00C91342 /* ApplicationThemeService.swift in Sources */,
 				2C3796122877001700C197E2 /* ProfileHeaderView.swift in Sources */,
 				2C725B5E28090D1F00A49043 /* View+Border.swift in Sources */,
@@ -5264,7 +5275,6 @@
 				2C58DE2B2803DEE2002A2774 /* VerticalCenteredScrollView.swift in Sources */,
 				2CD48D892858657100CFCC4A /* StepQuizView.swift in Sources */,
 				2CD4148729A8D92000ACA855 /* CodeInputPasteControl.swift in Sources */,
-				2C8E4FA12848BF1F0011ADFA /* StepQuizTableSelectColumnsViewController.swift in Sources */,
 				2C078CE72AE26E2000D97E24 /* UIKitSeparatorView.swift in Sources */,
 				2C677D022C4A3F860019AF03 /* StepQuizCodeBlanksSuggestionsView.swift in Sources */,
 				2C1F5877280D2B4800372A37 /* ApplicationInfo.swift in Sources */,
@@ -5290,6 +5300,7 @@
 				2CAE8D0528055D8300E6C83D /* StepTheoryActionButton.swift in Sources */,
 				2C45E7BD2A0FD9D600DFF32D /* ProjectSelectionListGridCellProjectLevelView.swift in Sources */,
 				2C37960F2876F36F00C197E2 /* ProfileViewData.swift in Sources */,
+				2C065AA22C52347E00B820F5 /* StepQuizTableSelectColumnsColumnView.swift in Sources */,
 				2C2600852A1FF8B000BD3D39 /* AppPowerModeObserver.swift in Sources */,
 				2C2D4930281151EB00753F16 /* AuthCredentialsViewModel.swift in Sources */,
 				E94BC944291E8BF5000B18D3 /* TopicsRepetitionsInfoBlock.swift in Sources */,
@@ -5441,6 +5452,7 @@
 				2C80D503288C5EBB00B2CD1E /* StepQuizCodeNavigationState.swift in Sources */,
 				2C32374D2837F7190062CAF6 /* Images.swift in Sources */,
 				2C23C00A2879EB1E0083709F /* StreakViewBuilder.swift in Sources */,
+				2C065AA42C523EE900B820F5 /* StepQuizTableSelectColumnsHeaderView.swift in Sources */,
 				2C1F588B280D8C8600372A37 /* GoogleSocialAuthSDKProvider.swift in Sources */,
 				2C84E7082C47BA11002EE787 /* StepQuizCodeBlanksViewModel.swift in Sources */,
 				E99B21832887E996006A6154 /* StepQuizChoiceSkeletonView.swift in Sources */,
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/UIKit/UIStackView+RemoveAllArrangedSubviews.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/UIKit/UIStackView+RemoveAllArrangedSubviews.swift
new file mode 100644
index 0000000000..20e87a385e
--- /dev/null
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Extensions/UIKit/UIStackView+RemoveAllArrangedSubviews.swift
@@ -0,0 +1,10 @@
+import UIKit
+
+extension UIStackView {
+    func removeAllArrangedSubviews() {
+        for subview in arrangedSubviews {
+            removeArrangedSubview(subview)
+            subview.removeFromSuperview()
+        }
+    }
+}
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTable/StepQuizTableViewData.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTable/StepQuizTableViewData.swift
index f15c75914f..09ec83becf 100644
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTable/StepQuizTableViewData.swift
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTable/StepQuizTableViewData.swift
@@ -16,7 +16,7 @@ struct StepQuizTableViewData {
         }
     }
 
-    struct Column: Identifiable {
+    struct Column: Identifiable, Equatable {
         let id: Int
         let text: String
 
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/StepQuizTableSelectColumnsColumnView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/StepQuizTableSelectColumnsColumnView.swift
new file mode 100644
index 0000000000..a3e048a46c
--- /dev/null
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/StepQuizTableSelectColumnsColumnView.swift
@@ -0,0 +1,207 @@
+import BEMCheckBox
+import SnapKit
+import UIKit
+
+extension StepQuizTableSelectColumnsColumnView {
+    struct Appearance {
+        var checkBoxBoxType: BEMBoxType = .circle
+        let checkBoxLineWidth: CGFloat = 2
+        let checkBoxAnimationDuration: CGFloat = 0.5
+        let checkBoxTintColor = ColorPalette.primary
+        let checkBoxOnCheckColor = UIColor.white
+        let checkBoxOnFillColor = ColorPalette.primary
+        let checkBoxOnTintColor = ColorPalette.primary
+        let checkBoxWidthHeight: CGFloat = 20
+        let checkBoxInsets = LayoutInsets(leading: 16)
+
+        let titleFont = UIFont.preferredFont(forTextStyle: .body)
+        let titleTextColor = UIColor.primaryText
+        let titleInsets = LayoutInsets.default
+
+        let contentViewMinHeight: CGFloat = 44
+
+        let backgroundColor = UIColor.clear
+    }
+}
+
+final class StepQuizTableSelectColumnsColumnView: UIControl {
+    let appearance: Appearance
+
+    private lazy var checkBox: BEMCheckBox = {
+        let checkBox = BEMCheckBox()
+        checkBox.lineWidth = appearance.checkBoxLineWidth
+        checkBox.hideBox = false
+        checkBox.boxType = appearance.checkBoxBoxType
+        checkBox.tintColor = appearance.checkBoxTintColor
+        checkBox.onCheckColor = appearance.checkBoxOnCheckColor
+        checkBox.onFillColor = appearance.checkBoxOnFillColor
+        checkBox.onTintColor = appearance.checkBoxOnTintColor
+        checkBox.animationDuration = appearance.checkBoxAnimationDuration
+        checkBox.onAnimationType = .fill
+        checkBox.offAnimationType = .fill
+        return checkBox
+    }()
+
+    private lazy var titleProcessedContentView: ProcessedContentView = {
+        let processedContentViewAppearance = ProcessedContentView.Appearance(
+            labelFont: appearance.titleFont,
+            labelTextColor: appearance.titleTextColor,
+            backgroundColor: .clear
+        )
+
+        let contentProcessor = ContentProcessor(
+            injections: ContentProcessor.defaultInjections + [
+                FontInjection(font: appearance.titleFont),
+                TextColorInjection(dynamicColor: appearance.titleTextColor)
+            ]
+        )
+
+        let processedContentView = ProcessedContentView(
+            frame: .zero,
+            appearance: processedContentViewAppearance,
+            contentProcessor: contentProcessor,
+            htmlToAttributedStringConverter: HTMLToAttributedStringConverter(font: appearance.titleFont)
+        )
+        processedContentView.delegate = self
+
+        return processedContentView
+    }()
+
+    private lazy var contentView = UIView()
+
+    private lazy var tapProxyView = UIKitTapProxyView(targetView: self)
+
+    var isOn: Bool { checkBox.on }
+
+    var onValueChanged: ((Bool) -> Void)?
+
+    var onContentLoad: (() -> Void)?
+
+    override var isHighlighted: Bool {
+        didSet {
+            titleProcessedContentView.alpha = isHighlighted ? 0.5 : 1.0
+        }
+    }
+
+    override var intrinsicContentSize: CGSize {
+        let titleProcessedContentViewIntrinsicContentSize = titleProcessedContentView.intrinsicContentSize
+        let titleProcessedContentViewHeightWithInsets = titleProcessedContentViewIntrinsicContentSize.height
+            + appearance.titleInsets.top
+            + appearance.titleInsets.bottom
+
+        let height = max(appearance.contentViewMinHeight, titleProcessedContentViewHeightWithInsets)
+
+        return CGSize(width: UIView.noIntrinsicMetric, height: height)
+    }
+
+    init(
+        frame: CGRect = .zero,
+        appearance: Appearance = Appearance()
+    ) {
+        self.appearance = appearance
+        super.init(frame: frame)
+
+        self.setupView()
+        self.addSubviews()
+        self.makeConstraints()
+    }
+
+    @available(*, unavailable)
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    override func layoutSubviews() {
+        super.layoutSubviews()
+        invalidateIntrinsicContentSize()
+    }
+
+    func setOn(_ isOn: Bool, animated: Bool) {
+        checkBox.setOn(isOn, animated: animated)
+    }
+
+    func setTitle(_ title: String) {
+        titleProcessedContentView.setText(title)
+    }
+
+    @objc
+    private func clicked() {
+        let newValue = !checkBox.on
+        onValueChanged?(newValue)
+    }
+}
+
+extension StepQuizTableSelectColumnsColumnView: ProgrammaticallyInitializableViewProtocol {
+    func setupView() {
+        backgroundColor = appearance.backgroundColor
+        contentView.backgroundColor = appearance.backgroundColor
+
+        addTarget(self, action: #selector(clicked), for: .touchUpInside)
+    }
+
+    func addSubviews() {
+        addSubview(contentView)
+        contentView.addSubview(checkBox)
+        contentView.addSubview(titleProcessedContentView)
+
+        addSubview(tapProxyView)
+    }
+
+    func makeConstraints() {
+        contentView.translatesAutoresizingMaskIntoConstraints = false
+        contentView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+            make.height.greaterThanOrEqualTo(appearance.contentViewMinHeight)
+        }
+
+        checkBox.translatesAutoresizingMaskIntoConstraints = false
+        checkBox.snp.makeConstraints { make in
+            make.leading.equalToSuperview().offset(appearance.checkBoxInsets.leading)
+            make.centerY.equalToSuperview()
+            make.width.height.equalTo(appearance.checkBoxWidthHeight)
+        }
+
+        titleProcessedContentView.translatesAutoresizingMaskIntoConstraints = false
+        titleProcessedContentView.snp.makeConstraints { make in
+            make.top.greaterThanOrEqualToSuperview().offset(appearance.titleInsets.top)
+            make.leading.equalTo(checkBox.snp.trailing).offset(appearance.titleInsets.leading)
+            make.bottom.lessThanOrEqualToSuperview().offset(-appearance.titleInsets.bottom)
+            make.trailing.equalToSuperview().offset(-appearance.titleInsets.trailing)
+            make.centerY.equalToSuperview()
+        }
+
+        tapProxyView.translatesAutoresizingMaskIntoConstraints = false
+        tapProxyView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+    }
+}
+
+extension StepQuizTableSelectColumnsColumnView: ProcessedContentViewDelegate {
+    func processedContentViewDidLoadContent(_ view: ProcessedContentView) {
+        invalidateIntrinsicContentSize()
+        onContentLoad?()
+    }
+
+    func processedContentView(_ view: ProcessedContentView, didReportNewHeight height: Int) {
+        invalidateIntrinsicContentSize()
+    }
+
+    func processedContentView(_ view: ProcessedContentView, didOpenImageURL url: URL) {}
+
+    func processedContentView(_ view: ProcessedContentView, didOpenLink url: URL) {}
+}
+
+#if DEBUG
+@available(iOS 17.0, *)
+#Preview {
+    let view = StepQuizTableSelectColumnsColumnView()
+    view.setTitle("test")
+
+    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
+        view.setOn(true, animated: true)
+    }
+
+    return view
+}
+#endif
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/StepQuizTableSelectColumnsHeaderView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/StepQuizTableSelectColumnsHeaderView.swift
new file mode 100644
index 0000000000..52abef5703
--- /dev/null
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/StepQuizTableSelectColumnsHeaderView.swift
@@ -0,0 +1,164 @@
+import SnapKit
+import UIKit
+
+extension StepQuizTableSelectColumnsHeaderView {
+    struct Appearance {
+        let promptFont = UIFont.preferredFont(forTextStyle: .caption1)
+        let promptTextColor = UIColor.secondaryText
+
+        let titleFont = UIFont.preferredFont(forTextStyle: .body)
+        let titleTextColor = UIColor.primaryText
+
+        let contentStackViewSpacing: CGFloat = LayoutInsets.defaultInset
+        let contentStackViewInsets = LayoutInsets.default.uiEdgeInsets
+
+        let backgroundColor = UIColor.clear
+    }
+}
+
+final class StepQuizTableSelectColumnsHeaderView: UIView {
+    let appearance: Appearance
+
+    private lazy var promptLabel: UILabel = {
+        let label = UILabel()
+        label.font = appearance.promptFont
+        label.textColor = appearance.promptTextColor
+        label.numberOfLines = 1
+        label.textAlignment = .center
+        return label
+    }()
+
+    private lazy var titleProcessedContentView: ProcessedContentView = {
+        let processedContentViewAppearance = ProcessedContentView.Appearance(
+            labelFont: appearance.titleFont,
+            labelTextColor: appearance.titleTextColor,
+            backgroundColor: .clear
+        )
+
+        let contentProcessor = ContentProcessor(
+            injections: ContentProcessor.defaultInjections + [
+                FontInjection(font: appearance.titleFont),
+                TextColorInjection(dynamicColor: appearance.titleTextColor)
+            ]
+        )
+
+        let processedContentView = ProcessedContentView(
+            frame: .zero,
+            appearance: processedContentViewAppearance,
+            contentProcessor: contentProcessor,
+            htmlToAttributedStringConverter: HTMLToAttributedStringConverter(font: appearance.titleFont)
+        )
+        processedContentView.delegate = self
+
+        return processedContentView
+    }()
+
+    private lazy var contentStackView: UIStackView = {
+        let stackView = UIStackView()
+        stackView.axis = .vertical
+        stackView.spacing = appearance.contentStackViewSpacing
+        return stackView
+    }()
+
+    private lazy var separatorView = UIKitSeparatorView()
+
+    var prompt: String? {
+        didSet {
+            promptLabel.text = prompt
+            promptLabel.isHidden = prompt?.isEmpty ?? true
+        }
+    }
+
+    var title: String? {
+        didSet {
+            titleProcessedContentView.setText(title)
+        }
+    }
+
+    override var intrinsicContentSize: CGSize {
+        let contentStackViewIntrinsicContentSize = contentStackView
+            .systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
+        let contentStackViewHeightWithInsets = contentStackViewIntrinsicContentSize.height
+            + appearance.contentStackViewInsets.top
+            + appearance.contentStackViewInsets.bottom
+
+        let height = contentStackViewHeightWithInsets.rounded(.up)
+
+        return CGSize(width: UIView.noIntrinsicMetric, height: height)
+    }
+
+    var onContentLoad: (() -> Void)?
+
+    init(
+        frame: CGRect = .zero,
+        appearance: Appearance = Appearance()
+    ) {
+        self.appearance = appearance
+        super.init(frame: frame)
+
+        self.setupView()
+        self.addSubviews()
+        self.makeConstraints()
+    }
+
+    @available(*, unavailable)
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    override func layoutSubviews() {
+        super.layoutSubviews()
+        invalidateIntrinsicContentSize()
+    }
+}
+
+extension StepQuizTableSelectColumnsHeaderView: ProgrammaticallyInitializableViewProtocol {
+    func setupView() {
+        backgroundColor = appearance.backgroundColor
+    }
+
+    func addSubviews() {
+        addSubview(contentStackView)
+        contentStackView.addArrangedSubview(promptLabel)
+        contentStackView.addArrangedSubview(titleProcessedContentView)
+
+        addSubview(separatorView)
+    }
+
+    func makeConstraints() {
+        contentStackView.translatesAutoresizingMaskIntoConstraints = false
+        contentStackView.snp.makeConstraints { make in
+            make.edges.equalToSuperview().inset(appearance.contentStackViewInsets)
+        }
+
+        separatorView.translatesAutoresizingMaskIntoConstraints = false
+        separatorView.snp.makeConstraints { make in
+            make.leading.bottom.trailing.equalToSuperview()
+        }
+    }
+}
+
+extension StepQuizTableSelectColumnsHeaderView: ProcessedContentViewDelegate {
+    func processedContentViewDidLoadContent(_ view: ProcessedContentView) {
+        invalidateIntrinsicContentSize()
+        onContentLoad?()
+    }
+
+    func processedContentView(_ view: ProcessedContentView, didReportNewHeight height: Int) {
+        invalidateIntrinsicContentSize()
+    }
+
+    func processedContentView(_ view: ProcessedContentView, didOpenImageURL url: URL) {}
+
+    func processedContentView(_ view: ProcessedContentView, didOpenLink url: URL) {}
+}
+
+#if DEBUG
+@available(iOS 17.0, *)
+#Preview {
+    let view = StepQuizTableSelectColumnsHeaderView()
+    view.prompt = Strings.StepQuizTable.multipleChoicePrompt
+    view.title = "Title goes here"
+    return view
+}
+#endif
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/StepQuizTableSelectColumnsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/StepQuizTableSelectColumnsView.swift
new file mode 100644
index 0000000000..b58c8ac8ce
--- /dev/null
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/StepQuizTableSelectColumnsView.swift
@@ -0,0 +1,185 @@
+import SnapKit
+import UIKit
+
+protocol StepQuizTableSelectColumnsViewDelegate: AnyObject {
+    func tableQuizSelectColumnsView(
+        _ view: StepQuizTableSelectColumnsView,
+        didSelectColumn column: StepQuizTableViewData.Column,
+        isOn: Bool
+    )
+    func tableQuizSelectColumnsViewDidTapConfirm(_ view: StepQuizTableSelectColumnsView)
+    func tableQuizSelectColumnsViewDidLoadContent(_ view: StepQuizTableSelectColumnsView)
+}
+
+extension StepQuizTableSelectColumnsView {
+    struct Appearance {
+        let backgroundColor = UIColor.systemBackground
+
+        let confirmButtonInsets = LayoutInsets.default.uiEdgeInsets
+    }
+}
+
+final class StepQuizTableSelectColumnsView: UIView {
+    let appearance: Appearance
+
+    weak var delegate: StepQuizTableSelectColumnsViewDelegate?
+
+    private lazy var headerView = StepQuizTableSelectColumnsHeaderView()
+
+    private lazy var columnsStackView: UIStackView = {
+        let stackView = UIStackView()
+        stackView.axis = .vertical
+        return stackView
+    }()
+
+    private lazy var confirmButton: UIButton = {
+        let button = UIKitRoundedRectangleButton(style: .violet)
+        button.addTarget(self, action: #selector(confirmButtonTapped), for: .touchUpInside)
+        button.setTitle(Strings.StepQuizTable.confirmButton, for: .normal)
+        return button
+    }()
+    private lazy var confirmButtonContainerView = UIView()
+
+    private lazy var scrollableContentStackView = UIKitScrollableStackView(orientation: .vertical)
+
+    private var loadGroup: DispatchGroup?
+
+    private var columns = [StepQuizTableViewData.Column]()
+    private var selectedColumnsIDs = Set<Int>()
+
+    var prompt: String? {
+        didSet {
+            headerView.prompt = prompt
+        }
+    }
+
+    var isMultipleChoice = false
+
+    init(
+        frame: CGRect = .zero,
+        appearance: Appearance = Appearance()
+    ) {
+        self.appearance = appearance
+        super.init(frame: frame)
+
+        self.setupView()
+        self.addSubviews()
+        self.makeConstraints()
+    }
+
+    @available(*, unavailable)
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    func set(
+        title: String,
+        columns: [StepQuizTableViewData.Column],
+        selectedColumnsIDs: Set<Int>
+    ) {
+        loadGroup = DispatchGroup()
+        let enterCount = columns.count + 1 // title + columns
+        for _ in 0..<enterCount {
+            loadGroup?.enter()
+        }
+        loadGroup?.notify(queue: .main) { [weak self] in
+            // dispatch_group_leave call isn't balanced with dispatch_group_enter, deinit dispatch_group_t here to
+            // prevent possible future call to leave onContentLoad.
+            guard let strongSelf = self else {
+                return
+            }
+
+            strongSelf.loadGroup = nil
+            strongSelf.delegate?.tableQuizSelectColumnsViewDidLoadContent(strongSelf)
+        }
+
+        headerView.onContentLoad = { [weak self] in
+            self?.loadGroup?.leave()
+        }
+        headerView.title = title
+
+        self.columns = columns
+        self.selectedColumnsIDs = selectedColumnsIDs
+
+        if !columnsStackView.arrangedSubviews.isEmpty {
+            columnsStackView.removeAllArrangedSubviews()
+        }
+
+        for column in columns {
+            let columnView = StepQuizTableSelectColumnsColumnView(
+                appearance: .init(checkBoxBoxType: isMultipleChoice ? .square : .circle)
+            )
+            columnView.onValueChanged = { [weak self] isOn in
+                guard let strongSelf = self else {
+                    return
+                }
+
+                strongSelf.delegate?.tableQuizSelectColumnsView(strongSelf, didSelectColumn: column, isOn: isOn)
+            }
+            columnView.tag = column.id
+            columnView.onContentLoad = { [weak self] in
+                self?.loadGroup?.leave()
+            }
+
+            columnsStackView.addArrangedSubview(columnView)
+
+            columnView.setOn(selectedColumnsIDs.contains(column.id), animated: false)
+            columnView.setTitle(column.text)
+        }
+    }
+
+    func update(selectedColumnsIDs: Set<Int>) {
+        self.selectedColumnsIDs = selectedColumnsIDs
+
+        for arrangedSubview in columnsStackView.arrangedSubviews {
+            guard let columnView = arrangedSubview as? StepQuizTableSelectColumnsColumnView else {
+                continue
+            }
+
+            let id = columnView.tag
+            let isOn = selectedColumnsIDs.contains(id)
+
+            let animated = columnView.isOn != isOn
+
+            columnView.setOn(isOn, animated: animated)
+        }
+    }
+
+    @objc
+    private func confirmButtonTapped() {
+        delegate?.tableQuizSelectColumnsViewDidTapConfirm(self)
+    }
+}
+
+extension StepQuizTableSelectColumnsView: ProgrammaticallyInitializableViewProtocol {
+    func setupView() {
+        backgroundColor = appearance.backgroundColor
+    }
+
+    func addSubviews() {
+        addSubview(scrollableContentStackView)
+
+        scrollableContentStackView.addArrangedView(headerView)
+        scrollableContentStackView.addArrangedView(columnsStackView)
+        scrollableContentStackView.addArrangedView(confirmButtonContainerView)
+
+        confirmButtonContainerView.addSubview(confirmButton)
+    }
+
+    func makeConstraints() {
+        scrollableContentStackView.translatesAutoresizingMaskIntoConstraints = false
+        scrollableContentStackView.snp.makeConstraints { make in
+            make.top.bottom.equalToSuperview()
+            make.leading.trailing.equalTo(safeAreaLayoutGuide)
+        }
+
+        confirmButton.translatesAutoresizingMaskIntoConstraints = false
+        confirmButton.snp.makeConstraints { make in
+            make.edges.equalToSuperview().inset(appearance.confirmButtonInsets)
+        }
+    }
+}
+
+extension StepQuizTableSelectColumnsView: PanModalScrollable {
+    var panScrollable: UIScrollView? { scrollableContentStackView.panScrollable }
+}
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/StepQuizTableSelectColumnsViewController.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/StepQuizTableSelectColumnsViewController.swift
index 44ca4ba8cc..9cc9e3389e 100644
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/StepQuizTableSelectColumnsViewController.swift
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/StepQuizTableSelectColumnsViewController.swift
@@ -1,5 +1,4 @@
 import PanModal
-import SwiftUI
 import UIKit
 
 extension StepQuizTableSelectColumnsViewController {
@@ -8,12 +7,19 @@ extension StepQuizTableSelectColumnsViewController {
     }
 }
 
-final class StepQuizTableSelectColumnsViewController: PanModalSwiftUIViewController<StepQuizTableSelectColumnsView> {
+final class StepQuizTableSelectColumnsViewController: PanModalPresentableViewController {
+    private let rowTitle: String
     private let columns: [StepQuizTableViewData.Column]
     private var selectedColumnsIDs: Set<Int>
     private let isMultipleChoice: Bool
     private let onColumnsSelected: (Set<Int>) -> Void
 
+    var tableQuizSelectColumnsView: StepQuizTableSelectColumnsView? { self.view as? StepQuizTableSelectColumnsView }
+
+    override var panScrollable: UIScrollView? { tableQuizSelectColumnsView?.panScrollable }
+
+    override var shortFormHeight: PanModalHeight { longFormHeight }
+
     init(
         title: String,
         columns: [StepQuizTableViewData.Column],
@@ -21,43 +27,76 @@ final class StepQuizTableSelectColumnsViewController: PanModalSwiftUIViewControl
         isMultipleChoice: Bool,
         onColumnsSelected: @escaping (Set<Int>) -> Void
     ) {
+        self.rowTitle = title
         self.columns = columns
         self.selectedColumnsIDs = selectedColumnsIDs
         self.isMultipleChoice = isMultipleChoice
         self.onColumnsSelected = onColumnsSelected
 
+        super.init()
+    }
+
+    override func loadView() {
+        let view = StepQuizTableSelectColumnsView(frame: UIScreen.main.bounds)
+        self.view = view
+        view.delegate = self
+    }
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+
         let prompt = isMultipleChoice
             ? Strings.StepQuizTable.multipleChoicePrompt
             : Strings.StepQuizTable.singleChoicePrompt
+        tableQuizSelectColumnsView?.prompt = prompt
+        tableQuizSelectColumnsView?.isMultipleChoice = isMultipleChoice
 
-        var view = StepQuizTableSelectColumnsView(
-            prompt: prompt,
-            title: title,
+        tableQuizSelectColumnsView?.set(
+            title: rowTitle,
             columns: columns,
-            selectedColumnsIDs: selectedColumnsIDs,
-            isMultipleChoice: isMultipleChoice
+            selectedColumnsIDs: selectedColumnsIDs
         )
 
-        super.init(
-            isPresented: .constant(false),
-            content: { view }
-        )
-
-        view.onColumnsChanged = self.handleColumnsChanged(_:)
-        view.onConfirmTapped = self.finishColumnsSelection
+        panModalSetNeedsLayoutUpdate()
     }
+}
+
+extension StepQuizTableSelectColumnsViewController: StepQuizTableSelectColumnsViewDelegate {
+    func tableQuizSelectColumnsView(
+        _ view: StepQuizTableSelectColumnsView,
+        didSelectColumn column: StepQuizTableViewData.Column,
+        isOn: Bool
+    ) {
+        if isMultipleChoice {
+            if isOn {
+                selectedColumnsIDs.insert(column.id)
+            } else {
+                selectedColumnsIDs.remove(column.id)
+            }
+        } else {
+            assert(selectedColumnsIDs.count <= 1, "Sigle choice")
+            selectedColumnsIDs.removeAll()
 
-    // MARK: Private API
+            if isOn {
+                selectedColumnsIDs.insert(column.id)
+            }
+        }
 
-    private func handleColumnsChanged(_ newColumns: Set<Int>) {
-        self.selectedColumnsIDs = newColumns
+        tableQuizSelectColumnsView?.update(selectedColumnsIDs: selectedColumnsIDs)
     }
 
-    private func finishColumnsSelection() {
-        self.onColumnsSelected(self.selectedColumnsIDs)
+    func tableQuizSelectColumnsViewDidTapConfirm(_ view: StepQuizTableSelectColumnsView) {
+        onColumnsSelected(selectedColumnsIDs)
 
         DispatchQueue.main.asyncAfter(deadline: .now() + Animation.dismissAnimationDelay) {
             self.dismiss(animated: true)
         }
     }
+
+    func tableQuizSelectColumnsViewDidLoadContent(_ view: StepQuizTableSelectColumnsView) {
+        DispatchQueue.main.async {
+            self.panModalSetNeedsLayoutUpdate()
+            self.panModalTransition(to: .longForm)
+        }
+    }
 }
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/Views/StepQuizTableSelectColumnsColumnView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/Views/StepQuizTableSelectColumnsColumnView.swift
deleted file mode 100644
index ce296b8953..0000000000
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/Views/StepQuizTableSelectColumnsColumnView.swift
+++ /dev/null
@@ -1,74 +0,0 @@
-import SwiftUI
-
-extension StepQuizTableSelectColumnsColumnView {
-    struct Appearance {
-        let interItemSpacing = LayoutInsets.smallInset
-
-        let checkboxIndicatorWidthHeight: CGFloat = 18
-        let radioIndicatorWidthHeight: CGFloat = 20
-    }
-}
-
-struct StepQuizTableSelectColumnsColumnView: View {
-    private(set) var appearance = Appearance()
-
-    let isSelected: Bool
-
-    let text: String
-
-    var isMultipleChoice: Bool
-
-    var onTap: () -> Void
-
-    var body: some View {
-        Button(action: onTap) {
-            HStack(spacing: appearance.interItemSpacing) {
-                buildIndicator(isSelected: isSelected, onTap: onTap)
-
-                LatexView(
-                    text: text,
-                    configuration: .quizContent()
-                )
-            }
-        }
-    }
-
-    @ViewBuilder
-    private func buildIndicator(isSelected: Bool, onTap: @escaping () -> Void) -> some View {
-        if isMultipleChoice {
-            CheckboxButton(
-                appearance: .init(backgroundUnselectedColor: .clear),
-                isSelected: isSelected,
-                onClick: onTap
-            )
-            .frame(widthHeight: appearance.checkboxIndicatorWidthHeight)
-        } else {
-            RadioButton(
-                appearance: .init(indicatorUnselectedColor: .clear, backgroundColor: .clear),
-                isSelected: isSelected,
-                onClick: onTap
-            )
-            .frame(widthHeight: appearance.radioIndicatorWidthHeight)
-        }
-    }
-}
-
-#if DEBUG
-#Preview {
-    VStack {
-        StepQuizTableSelectColumnsColumnView(
-            isSelected: true,
-            text: "Some option",
-            isMultipleChoice: false,
-            onTap: {}
-        )
-        StepQuizTableSelectColumnsColumnView(
-            isSelected: true,
-            text: "Some option",
-            isMultipleChoice: true,
-            onTap: {}
-        )
-    }
-    .padding()
-}
-#endif
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/Views/StepQuizTableSelectColumnsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/Views/StepQuizTableSelectColumnsView.swift
deleted file mode 100644
index daac6c1973..0000000000
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizTableSelectColumns/Views/StepQuizTableSelectColumnsView.swift
+++ /dev/null
@@ -1,105 +0,0 @@
-import SwiftUI
-
-struct StepQuizTableSelectColumnsView: View {
-    let prompt: String
-    let title: String
-
-    let columns: [StepQuizTableViewData.Column]
-    @State private(set) var selectedColumnsIDs: Set<Int>
-
-    let isMultipleChoice: Bool
-
-    var onColumnsChanged: ((Set<Int>) -> Void)?
-    var onConfirmTapped: (() -> Void)?
-
-    var body: some View {
-        VStack(spacing: LayoutInsets.defaultInset) {
-            Text(prompt)
-                .font(.caption)
-                .foregroundColor(.primaryText)
-
-            VStack(alignment: .leading, spacing: 0) {
-                LatexView(
-                    text: title,
-                    configuration: .quizContent()
-                )
-                .padding(.bottom, LayoutInsets.smallInset)
-
-                VStack(alignment: .leading, spacing: 0) {
-                    ForEach(columns) { column in
-                        StepQuizTableSelectColumnsColumnView(
-                            isSelected: selectedColumnsIDs.contains(column.id),
-                            text: column.text,
-                            isMultipleChoice: isMultipleChoice,
-                            onTap: {
-                                handleColumnTapped(column: column)
-                            }
-                        )
-                        .padding(.vertical, LayoutInsets.smallInset)
-                    }
-                }
-                .padding(.bottom, LayoutInsets.smallInset)
-
-                Button(Strings.StepQuizTable.confirmButton, action: { onConfirmTapped?() })
-                    .buttonStyle(RoundedRectangleButtonStyle(style: .violet))
-                    .padding(.vertical)
-
-                Spacer()
-            }
-        }
-        .padding()
-        .ignoresSafeArea()
-    }
-
-    private func handleColumnTapped(column: StepQuizTableViewData.Column) {
-        let isContains = selectedColumnsIDs.contains(column.id)
-
-        if isMultipleChoice {
-            if isContains {
-                selectedColumnsIDs.remove(column.id)
-            } else {
-                selectedColumnsIDs.insert(column.id)
-            }
-        } else {
-            assert(selectedColumnsIDs.count <= 1, "Sigle choice")
-            selectedColumnsIDs.removeAll()
-
-            if !isContains {
-                selectedColumnsIDs.insert(column.id)
-            }
-        }
-
-        onColumnsChanged?(selectedColumnsIDs)
-    }
-}
-
-struct StepQuizTableSelectColumnsView_Previews: PreviewProvider {
-    static var previews: some View {
-        Group {
-            StepQuizTableSelectColumnsView(
-                prompt: "Choose from the table",
-                title: "Variant A",
-                columns: [
-                    .init(text: "1"),
-                    .init(text: "2"),
-                    .init(text: "3")
-                ],
-                selectedColumnsIDs: ["2".hashValue],
-                isMultipleChoice: false,
-                onColumnsChanged: { _ in },
-                onConfirmTapped: {}
-            )
-
-            StepQuizTableSelectColumnsView(
-                prompt: "Choose one or multiple options",
-                title: "Variant A",
-                columns: [.init(text: "1"), .init(text: "2"), .init(text: "3")],
-                selectedColumnsIDs: ["1".hashValue, "2".hashValue],
-                isMultipleChoice: true,
-                onColumnsChanged: { _ in },
-                onConfirmTapped: {}
-            )
-        }
-        .previewLayout(.sizeThatFits)
-    }
-}
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Views/UIKit/UIKitScrollableStackView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Views/UIKit/UIKitScrollableStackView.swift
new file mode 100644
index 0000000000..59572c18a5
--- /dev/null
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Views/UIKit/UIKitScrollableStackView.swift
@@ -0,0 +1,303 @@
+import SnapKit
+import UIKit
+
+protocol UIKitScrollableStackViewDelegate: AnyObject {
+    func scrollableStackViewRefreshControlDidRefresh(_ scrollableStackView: UIKitScrollableStackView)
+}
+
+final class UIKitScrollableStackView: UIView {
+    private let orientation: Orientation
+
+    weak var delegate: UIKitScrollableStackViewDelegate?
+
+    private lazy var stackView: UIStackView = {
+        let stackView = UIStackView()
+        stackView.axis = orientation.stackViewOrientation
+        return stackView
+    }()
+
+    private lazy var scrollView = UIScrollView()
+
+    // MARK: - Refresh control
+
+    var isRefreshControlEnabled = false {
+        didSet {
+            guard oldValue != isRefreshControlEnabled else {
+                return
+            }
+
+            let refreshControl = isRefreshControlEnabled ? UIRefreshControl() : nil
+            if let refreshControl {
+                refreshControl.addTarget(
+                    self,
+                    action: #selector(onRefreshControlValueChanged),
+                    for: .valueChanged
+                )
+            }
+
+            scrollView.refreshControl = refreshControl
+        }
+    }
+
+    private var refreshControl: UIRefreshControl? {
+        scrollView.subviews.first(where: { $0 is UIRefreshControl }) as? UIRefreshControl
+    }
+
+    // MARK: - Blocks
+
+    var arrangedSubviews: [UIView] {
+        stackView.arrangedSubviews
+    }
+
+    // MARK: - Proxy properties
+
+    var showsHorizontalScrollIndicator: Bool {
+        get {
+            scrollView.showsHorizontalScrollIndicator
+        }
+        set {
+            scrollView.showsHorizontalScrollIndicator = newValue
+        }
+    }
+
+    var showsVerticalScrollIndicator: Bool {
+        get {
+            scrollView.showsVerticalScrollIndicator
+        }
+        set {
+            scrollView.showsVerticalScrollIndicator = newValue
+        }
+    }
+
+    var spacing: CGFloat {
+        get {
+            stackView.spacing
+        }
+        set {
+            stackView.spacing = newValue
+        }
+    }
+
+    var contentInsetAdjustmentBehavior: UIScrollView.ContentInsetAdjustmentBehavior {
+        get {
+            scrollView.contentInsetAdjustmentBehavior
+        }
+        set {
+            scrollView.contentInsetAdjustmentBehavior = newValue
+        }
+    }
+
+    var scrollDelegate: UIScrollViewDelegate? {
+        get {
+            scrollView.delegate
+        }
+        set {
+            scrollView.delegate = newValue
+        }
+    }
+
+    var contentInsets: UIEdgeInsets {
+        get {
+            scrollView.contentInset
+        }
+        set {
+            scrollView.contentInset = newValue
+        }
+    }
+
+    var contentOffset: CGPoint {
+        get {
+            scrollView.contentOffset
+        }
+        set {
+            scrollView.contentOffset = newValue
+        }
+    }
+
+    var verticalScrollIndicatorInsets: UIEdgeInsets {
+        get {
+            scrollView.verticalScrollIndicatorInsets
+        }
+        set {
+            scrollView.verticalScrollIndicatorInsets = newValue
+        }
+    }
+
+    var horizontalScrollIndicatorInsets: UIEdgeInsets {
+        get {
+            scrollView.horizontalScrollIndicatorInsets
+        }
+        set {
+            scrollView.horizontalScrollIndicatorInsets = newValue
+        }
+    }
+
+    var automaticallyAdjustsScrollIndicatorInsets: Bool {
+        get {
+            scrollView.automaticallyAdjustsScrollIndicatorInsets
+        }
+        set {
+            scrollView.automaticallyAdjustsScrollIndicatorInsets = newValue
+        }
+    }
+
+    var shouldBounce: Bool {
+        get {
+            scrollView.bounces
+        }
+        set {
+            scrollView.bounces = newValue
+        }
+    }
+
+    var isPagingEnabled: Bool {
+        get {
+            scrollView.isPagingEnabled
+        }
+        set {
+            scrollView.isPagingEnabled = newValue
+        }
+    }
+
+    var isScrollEnabled: Bool {
+        get {
+            scrollView.isScrollEnabled
+        }
+        set {
+            scrollView.isScrollEnabled = newValue
+        }
+    }
+
+    var contentSize: CGSize {
+        get {
+            scrollView.contentSize
+        }
+        set {
+            scrollView.contentSize = newValue
+        }
+    }
+
+    // MARK: - Inits
+
+    init(frame: CGRect = .zero, orientation: Orientation) {
+        self.orientation = orientation
+        super.init(frame: frame)
+
+        self.setupView()
+        self.addSubviews()
+        self.makeConstraints()
+    }
+
+    @available(*, unavailable)
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    // MARK: Public interface
+
+    func addArrangedView(_ view: UIView) {
+        stackView.addArrangedSubview(view)
+    }
+
+    func removeArrangedView(_ view: UIView) {
+        for subview in stackView.subviews where subview == view {
+            stackView.removeArrangedSubview(subview)
+            subview.removeFromSuperview()
+        }
+    }
+
+    func insertArrangedView(_ view: UIView, at index: Int) {
+        stackView.insertArrangedSubview(view, at: index)
+    }
+
+    func removeAllArrangedViews() {
+        for subview in stackView.subviews {
+            removeArrangedView(subview)
+        }
+    }
+
+    func startRefreshing() {
+        refreshControl?.beginRefreshing()
+    }
+
+    func endRefreshing() {
+        refreshControl?.endRefreshing()
+    }
+
+    func scrollTo(arrangedViewIndex: Int) {
+        guard let targetFrame = arrangedSubviews[safe: arrangedViewIndex]?.frame else {
+            return
+        }
+
+        scrollView.scrollRectToVisible(targetFrame, animated: true)
+    }
+
+    // MARK: - Private methods
+
+    @objc
+    private func onRefreshControlValueChanged() {
+        delegate?.scrollableStackViewRefreshControlDidRefresh(self)
+    }
+
+    enum Orientation {
+        case vertical
+        case horizontal
+
+        var stackViewOrientation: NSLayoutConstraint.Axis {
+            switch self {
+            case .vertical:
+                NSLayoutConstraint.Axis.vertical
+            case .horizontal:
+                NSLayoutConstraint.Axis.horizontal
+            }
+        }
+    }
+}
+
+extension UIKitScrollableStackView: ProgrammaticallyInitializableViewProtocol {
+    func setupView() {
+        stackView.clipsToBounds = false
+        scrollView.clipsToBounds = false
+
+        // For pull-to-refresh when contentSize is too small for scrolling
+        if orientation == .horizontal {
+            scrollView.alwaysBounceHorizontal = true
+        } else {
+            scrollView.alwaysBounceVertical = true
+        }
+        scrollView.bounces = true
+    }
+
+    func addSubviews() {
+        addSubview(scrollView)
+        scrollView.addSubview(stackView)
+    }
+
+    func makeConstraints() {
+        scrollView.translatesAutoresizingMaskIntoConstraints = false
+        scrollView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+
+        stackView.translatesAutoresizingMaskIntoConstraints = false
+        stackView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+
+            if case .vertical = orientation {
+                make.width.equalTo(scrollView.snp.width)
+            } else {
+                make.height.equalTo(scrollView.snp.height)
+            }
+        }
+    }
+}
+
+// MARK: - PanModalScrollable -
+
+protocol PanModalScrollable: AnyObject {
+    var panScrollable: UIScrollView? { get }
+}
+
+extension UIKitScrollableStackView: PanModalScrollable {
+    var panScrollable: UIScrollView? { scrollView }
+}
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Views/UIKit/UIKitTapProxyView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Views/UIKit/UIKitTapProxyView.swift
new file mode 100644
index 0000000000..d4e865cfab
--- /dev/null
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Views/UIKit/UIKitTapProxyView.swift
@@ -0,0 +1,14 @@
+import UIKit
+
+class UIKitTapProxyView: UIView {
+    var targetView: UIView?
+
+    convenience init(targetView: UIView) {
+        self.init()
+        self.targetView = targetView
+    }
+
+    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
+        bounds.contains(point) ? targetView : nil
+    }
+}
diff --git a/iosHyperskillApp/iosHyperskillAppTests/ExtensionsTests/UIStackViewExtensionTests.swift b/iosHyperskillApp/iosHyperskillAppTests/ExtensionsTests/UIStackViewExtensionTests.swift
new file mode 100644
index 0000000000..dd0f1d5589
--- /dev/null
+++ b/iosHyperskillApp/iosHyperskillAppTests/ExtensionsTests/UIStackViewExtensionTests.swift
@@ -0,0 +1,17 @@
+import XCTest
+
+@testable import iosHyperskillApp
+
+final class UIStackViewExtensionTests: XCTestCase {
+    func testRemoveAllArrangedSubviews() {
+        let stackView = UIStackView()
+        let view1 = UIView()
+        stackView.addArrangedSubview(view1)
+        let view2 = UIView()
+        stackView.addArrangedSubview(view2)
+
+        stackView.removeAllArrangedSubviews()
+
+        XCTAssertTrue(stackView.arrangedSubviews.isEmpty)
+    }
+}