Skip to content

Commit 87a5458

Browse files
Merge pull request #1 from mohamed-arradi/version/v1.0.0
IMSwitch 1.0.0
2 parents b258454 + a954bf2 commit 87a5458

File tree

8 files changed

+448
-1
lines changed

8 files changed

+448
-1
lines changed

IMSwitch/.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
xcuserdata/
5+
DerivedData/
6+
.swiftpm/configuration/registries.json
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
.netrc
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "1500"
4+
version = "1.7">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES">
8+
<BuildActionEntries>
9+
<BuildActionEntry
10+
buildForTesting = "YES"
11+
buildForRunning = "YES"
12+
buildForProfiling = "YES"
13+
buildForArchiving = "YES"
14+
buildForAnalyzing = "YES">
15+
<BuildableReference
16+
BuildableIdentifier = "primary"
17+
BlueprintIdentifier = "IMSwitch"
18+
BuildableName = "IMSwitch"
19+
BlueprintName = "IMSwitch"
20+
ReferencedContainer = "container:">
21+
</BuildableReference>
22+
</BuildActionEntry>
23+
<BuildActionEntry
24+
buildForTesting = "YES"
25+
buildForRunning = "YES"
26+
buildForProfiling = "NO"
27+
buildForArchiving = "NO"
28+
buildForAnalyzing = "YES">
29+
<BuildableReference
30+
BuildableIdentifier = "primary"
31+
BlueprintIdentifier = "IMSwitchTests"
32+
BuildableName = "IMSwitchTests"
33+
BlueprintName = "IMSwitchTests"
34+
ReferencedContainer = "container:">
35+
</BuildableReference>
36+
</BuildActionEntry>
37+
</BuildActionEntries>
38+
</BuildAction>
39+
<TestAction
40+
buildConfiguration = "Debug"
41+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
42+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
43+
shouldUseLaunchSchemeArgsEnv = "YES"
44+
shouldAutocreateTestPlan = "YES">
45+
<Testables>
46+
<TestableReference
47+
skipped = "NO">
48+
<BuildableReference
49+
BuildableIdentifier = "primary"
50+
BlueprintIdentifier = "IMSwitchTests"
51+
BuildableName = "IMSwitchTests"
52+
BlueprintName = "IMSwitchTests"
53+
ReferencedContainer = "container:">
54+
</BuildableReference>
55+
</TestableReference>
56+
</Testables>
57+
</TestAction>
58+
<LaunchAction
59+
buildConfiguration = "Debug"
60+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
61+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
62+
launchStyle = "0"
63+
useCustomWorkingDirectory = "NO"
64+
ignoresPersistentStateOnLaunch = "NO"
65+
debugDocumentVersioning = "YES"
66+
debugServiceExtension = "internal"
67+
allowLocationSimulation = "YES">
68+
</LaunchAction>
69+
<ProfileAction
70+
buildConfiguration = "Release"
71+
shouldUseLaunchSchemeArgsEnv = "YES"
72+
savedToolIdentifier = ""
73+
useCustomWorkingDirectory = "NO"
74+
debugDocumentVersioning = "YES">
75+
<MacroExpansion>
76+
<BuildableReference
77+
BuildableIdentifier = "primary"
78+
BlueprintIdentifier = "IMSwitch"
79+
BuildableName = "IMSwitch"
80+
BlueprintName = "IMSwitch"
81+
ReferencedContainer = "container:">
82+
</BuildableReference>
83+
</MacroExpansion>
84+
</ProfileAction>
85+
<AnalyzeAction
86+
buildConfiguration = "Debug">
87+
</AnalyzeAction>
88+
<ArchiveAction
89+
buildConfiguration = "Release"
90+
revealArchiveInOrganizer = "YES">
91+
</ArchiveAction>
92+
</Scheme>

IMSwitch/Package.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// swift-tools-version: 5.9
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "IMSwitch",
8+
platforms: [.iOS(.v15), .macOS(.v12)],
9+
products: [
10+
.library(
11+
name: "IMSwitch",
12+
targets: ["IMSwitch"]),
13+
],
14+
targets: [
15+
.target(
16+
name: "IMSwitch"),
17+
.testTarget(
18+
name: "IMSwitchTests",
19+
dependencies: ["IMSwitch"]),
20+
]
21+
)
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
import SwiftUI
2+
import Foundation
3+
4+
class ToggleState: ObservableObject {
5+
@Published var isPrimaryOptionSelected: Bool = false
6+
@Published var isRTL: Bool = false
7+
}
8+
9+
struct IMSwitch: View {
10+
@StateObject var toggleState: ToggleState
11+
@Environment(\.layoutDirection) var direction
12+
13+
private (set) var leftImage: Image
14+
private (set) var rightImage: Image
15+
private (set) var backgroundColor: Color
16+
private (set) var circleFilledColor: Color
17+
private (set) var animationType: Animation
18+
private (set) var animationLength: Double
19+
private (set) var activeColor: Color
20+
21+
init(leftImage: Image,
22+
rightImage: Image,
23+
activeColor: Color,
24+
circleFilledColor: Color,
25+
backgroundColor: Color,
26+
animation: Animation = .default,
27+
animationLength: Double = 0.35,
28+
toggleState: ToggleState) {
29+
self.circleFilledColor = circleFilledColor
30+
self.leftImage = leftImage
31+
self.rightImage = rightImage
32+
self.activeColor = activeColor
33+
self.backgroundColor = backgroundColor
34+
self.animationType = animation
35+
self.animationLength = animationLength
36+
self._toggleState = .init(wrappedValue: toggleState)
37+
}
38+
39+
var body: some View {
40+
GeometryReader { geometry in
41+
HStack {
42+
if !toggleState.isPrimaryOptionSelected {
43+
Spacer()
44+
}
45+
46+
Circle()
47+
.fill(circleFilledColor)
48+
.frame(width: geometry.size.width / 2,
49+
height: geometry.size.height)
50+
.simultaneousGesture(
51+
DragGesture()
52+
.onEnded { value in
53+
if value.translation.width < 0 {
54+
self.toggleState.isPrimaryOptionSelected = true
55+
} else {
56+
self.toggleState.isPrimaryOptionSelected = false
57+
}
58+
}
59+
)
60+
.onTapGesture {
61+
self.toggleState.isPrimaryOptionSelected.toggle()
62+
}
63+
64+
if toggleState.isPrimaryOptionSelected {
65+
Spacer()
66+
}
67+
}
68+
69+
.overlay(content: {
70+
HStack {
71+
leftImage
72+
.resizable()
73+
.scaledToFit()
74+
.frame(width: geometry.size.width / 4, height: geometry.size.height / 2)
75+
.foregroundColor(toggleState.isPrimaryOptionSelected ? activeColor : circleFilledColor)
76+
.onTapGesture {
77+
if !toggleState.isPrimaryOptionSelected {
78+
self.toggleState.isPrimaryOptionSelected.toggle()
79+
}
80+
}
81+
.simultaneousGesture(
82+
DragGesture()
83+
.onEnded { value in
84+
if value.translation.width < 0 {
85+
self.toggleState.isPrimaryOptionSelected = !toggleState.isRTL
86+
} else {
87+
self.toggleState.isPrimaryOptionSelected = toggleState.isRTL
88+
}
89+
}
90+
)
91+
Spacer()
92+
rightImage
93+
.resizable()
94+
.scaledToFit()
95+
.frame(width: geometry.size.width / 4, height: geometry.size.height / 2)
96+
.foregroundColor(toggleState.isPrimaryOptionSelected ? circleFilledColor : activeColor)
97+
.onTapGesture {
98+
if toggleState.isPrimaryOptionSelected {
99+
self.toggleState.isPrimaryOptionSelected.toggle()
100+
}
101+
}
102+
.simultaneousGesture(
103+
DragGesture()
104+
.onEnded { value in
105+
if value.translation.width < 0 {
106+
self.toggleState.isPrimaryOptionSelected = true
107+
} else {
108+
self.toggleState.isPrimaryOptionSelected = false
109+
}
110+
}
111+
)
112+
}
113+
.padding(EdgeInsets(top: geometry.size.height / 4,
114+
leading: geometry.size.width / 8,
115+
bottom: geometry.size.height / 4,
116+
trailing: geometry.size.width / 8))
117+
})
118+
.frame(width: geometry.size.width, height: geometry.size.height)
119+
.padding(EdgeInsets(top: 20, leading: 10, bottom: 20, trailing: 10))
120+
.background(backgroundColor)
121+
.animation(animationType, value: animationLength)
122+
.cornerRadius(geometry.size.height / 1.6)
123+
}
124+
.shadow(radius: 8)
125+
.onAppear {
126+
toggleState.isRTL = direction == .rightToLeft
127+
}
128+
}
129+
}
130+
131+
#if DEBUG
132+
struct IMSwitch_Previews: PreviewProvider {
133+
static var previews: some View {
134+
Group {
135+
Group {
136+
VStack(spacing: 50) {
137+
IMSwitch(
138+
leftImage: .init(systemName: "bicycle"),
139+
rightImage: .init(systemName: "parkingsign.circle"),
140+
activeColor: .white,
141+
circleFilledColor: .yellow,
142+
backgroundColor: Color.white.opacity(0.85), animation: .easeInOut,
143+
animationLength: 0.35, toggleState: .init())
144+
.frame(width: 200, height: 60)
145+
.environment(\.layoutDirection, .leftToRight)
146+
147+
IMSwitch(
148+
leftImage: .init(systemName: "car"),
149+
rightImage: .init(systemName: "parkingsign.circle"),
150+
activeColor: .white,
151+
circleFilledColor: .blue,
152+
backgroundColor: Color.white.opacity(0.85), animation: .easeInOut,
153+
animationLength: 0.35, toggleState: .init())
154+
.frame(width: 200, height: 60)
155+
.environment(\.layoutDirection, .leftToRight)
156+
157+
IMSwitch(
158+
leftImage: .init(systemName: "bus"),
159+
rightImage: .init(systemName: "train.side.front.car"),
160+
activeColor: .white,
161+
circleFilledColor: .green,
162+
backgroundColor: Color.white.opacity(0.85), animation: .easeInOut,
163+
animationLength: 0.35, toggleState: .init())
164+
.frame(width: 200, height: 60)
165+
.environment(\.layoutDirection, .leftToRight)
166+
167+
IMSwitch(
168+
leftImage: .init(systemName: "person.3"),
169+
rightImage: .init(systemName: "person"),
170+
activeColor: .white,
171+
circleFilledColor: .red,
172+
backgroundColor: Color.white.opacity(0.85), animation: .easeInOut,
173+
animationLength: 0.35, toggleState: .init())
174+
.frame(width: 200, height: 60)
175+
.environment(\.layoutDirection, .leftToRight)
176+
}
177+
}.previewDisplayName("Default preview 1")
178+
}
179+
Group {
180+
VStack(spacing: 50) {
181+
IMSwitch(
182+
leftImage: .init(systemName: "bicycle"),
183+
rightImage: .init(systemName: "parkingsign.circle"),
184+
activeColor: .white,
185+
circleFilledColor: .yellow,
186+
backgroundColor: Color.white.opacity(0.85), animation: .easeInOut,
187+
animationLength: 0.35, toggleState: .init())
188+
.frame(width: 200, height: 60)
189+
.environment(\.layoutDirection, .leftToRight)
190+
191+
IMSwitch(
192+
leftImage: .init(systemName: "car"),
193+
rightImage: .init(systemName: "parkingsign.circle"),
194+
activeColor: .white,
195+
circleFilledColor: .blue,
196+
backgroundColor: Color.white.opacity(0.85), animation: .easeInOut,
197+
animationLength: 0.35, toggleState: .init())
198+
.frame(width: 200, height: 60)
199+
.environment(\.layoutDirection, .leftToRight)
200+
201+
IMSwitch(
202+
leftImage: .init(systemName: "bus"),
203+
rightImage: .init(systemName: "train.side.front.car"),
204+
activeColor: .white,
205+
circleFilledColor: .green,
206+
backgroundColor: Color.white.opacity(0.85), animation: .easeInOut,
207+
animationLength: 0.35, toggleState: .init())
208+
.frame(width: 200, height: 60)
209+
.environment(\.layoutDirection, .leftToRight)
210+
211+
IMSwitch(
212+
leftImage: .init(systemName: "person.3"),
213+
rightImage: .init(systemName: "person"),
214+
activeColor: .white,
215+
circleFilledColor: .red,
216+
backgroundColor: Color.white.opacity(0.85), animation: .easeInOut,
217+
animationLength: 0.35, toggleState: .init())
218+
.frame(width: 200, height: 60)
219+
.environment(\.layoutDirection, .leftToRight)
220+
}
221+
}.previewDisplayName("Default preview 1")
222+
}
223+
}
224+
#endif
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import XCTest
2+
@testable import IMSwitch
3+
4+
final class MyLibraryTests: XCTestCase {}

0 commit comments

Comments
 (0)