Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,8 @@ function_body_length:
cyclomatic_complexity:
warning: 10
error: 20

identifier_name:
excluded:
- x
- y
24 changes: 24 additions & 0 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 48 additions & 16 deletions Sources/SlidableImage/SlidableImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,56 +7,88 @@ import SwiftUI

public struct SlidableImage<ArrowsIcon: View, LeftView: View, RightView: View>: View {
@State private var location: CGPoint?
private let axis: Axis
private let arrows: () -> ArrowsIcon
private let leftView: () -> LeftView
private let rightView: () -> RightView

public init(@ViewBuilder arrows: @escaping () -> ArrowsIcon,
public init(axis: Axis = .horizontal,
@ViewBuilder arrows: @escaping () -> ArrowsIcon,
@ViewBuilder leftView: @escaping () -> LeftView,
@ViewBuilder rightView: @escaping () -> RightView) {
self.axis = axis
self.arrows = arrows
self.leftView = leftView
self.rightView = rightView
}

public var body: some View {
GeometryReader { geometry in
ZStack(alignment: .leading) {
ZStack(alignment: axis == .horizontal ? .leading : .top) {
rightView()
leftView()
.mask {
HStack {
Color.black
Spacer(minLength: maskSize(width: geometry.size.width))
if axis == .horizontal {
HStack {
Color.black
Spacer(minLength: maskSize(total: geometry.size.width))
}
} else {
VStack {
Color.black
Spacer(minLength: maskSize(total: geometry.size.height))
}
}
}

arrows()
.frame(width: Constants.arrowSize, height: Constants.arrowSize)
.padding(.leading, location?.x ?? geometry.size.width / 2 - Constants.arrowSize / 2)
.padding(axis == .horizontal ? .leading : .top,
locationValue ?? defaultOffset(geometry: geometry))
.gesture(
DragGesture()
.onChanged { value in
guard value.location.x <= geometry.size.width - Constants.arrowSize else { return }

location = CGPoint(x: value.location.x, y: geometry.size.height / 2)
if axis == .horizontal {
let x = min(max(value.location.x, 0), geometry.size.width - Constants.arrowSize)
location = CGPoint(x: x, y: geometry.size.height / 2)
} else {
let y = min(max(value.location.y, 0), geometry.size.height - Constants.arrowSize)
location = CGPoint(x: geometry.size.width / 2, y: y)
}
}
)
}
}
}

package func maskSize(width: CGFloat, locationX: CGFloat? = nil) -> CGFloat {
guard let locationX = locationX ?? location?.x else {
return width / 2
private var locationValue: CGFloat? {
axis == .horizontal ? location?.x : location?.y
}

private func defaultOffset(geometry: GeometryProxy) -> CGFloat {
axis == .horizontal
? geometry.size.width / 2 - Constants.arrowSize / 2
: geometry.size.height / 2 - Constants.arrowSize / 2
}

package func maskSize(total: CGFloat, locationValue: CGFloat? = nil) -> CGFloat {
guard let value = locationValue ?? self.locationValue else {
return total / 2
}

return width - locationX - Constants.arrowSize / 2
return total - value - Constants.arrowSize / 2
}
}

#Preview {
SlidableImage(arrows: { Arrows() },
leftView: { Color.red },
rightView: { Color.green })
VStack {
SlidableImage(arrows: { Arrows() },
leftView: { Color.red },
rightView: { Color.green })

SlidableImage(axis: .vertical,
arrows: { Arrows() },
leftView: { Color.blue },
rightView: { Color.orange })
}
}
39 changes: 29 additions & 10 deletions Tests/SlidableImageTests/SlidableImageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,46 @@ import SwiftUI
@MainActor
struct SlidableImageTests {

private func makeSUT() -> SlidableImage<Arrows, Color, Color> {
SlidableImage(arrows: { Arrows() }, leftView: { Color.red }, rightView: { Color.green })
private func makeSUT(axis: Axis = .horizontal) -> SlidableImage<Arrows, Color, Color> {
SlidableImage(axis: axis, arrows: { Arrows() }, leftView: { Color.red }, rightView: { Color.green })
}

@Test func maskSizeWithoutLocation() {
#expect(makeSUT().maskSize(width: 200) == 100)
// MARK: - Horizontal

@Test func maskSizeHorizontalWithoutLocation() {
#expect(makeSUT().maskSize(total: 200) == 100)
}

@Test func maskSizeWithLocationAtStart() {
let result = makeSUT().maskSize(width: 200, locationX: 0)
@Test func maskSizeHorizontalWithLocationAtStart() {
let result = makeSUT().maskSize(total: 200, locationValue: 0)
#expect(result == 200 - Constants.arrowSize / 2)
}

@Test func maskSizeWithLocationAtCenter() {
let result = makeSUT().maskSize(width: 200, locationX: 100)
@Test func maskSizeHorizontalWithLocationAtCenter() {
let result = makeSUT().maskSize(total: 200, locationValue: 100)
#expect(result == 100 - Constants.arrowSize / 2)
}

@Test func maskSizeWithLocationAtBoundary() {
@Test func maskSizeHorizontalWithLocationAtBoundary() {
let locationX = 200 - Constants.arrowSize
let result = makeSUT().maskSize(width: 200, locationX: locationX)
let result = makeSUT().maskSize(total: 200, locationValue: locationX)
#expect(result == Constants.arrowSize / 2)
}

// MARK: - Vertical

@Test func maskSizeVerticalWithoutLocation() {
#expect(makeSUT(axis: .vertical).maskSize(total: 200) == 100)
}

@Test func maskSizeVerticalWithLocationAtStart() {
let result = makeSUT(axis: .vertical).maskSize(total: 200, locationValue: 0)
#expect(result == 200 - Constants.arrowSize / 2)
}

@Test func maskSizeVerticalWithLocationAtBoundary() {
let locationY = 200 - Constants.arrowSize
let result = makeSUT(axis: .vertical).maskSize(total: 200, locationValue: locationY)
#expect(result == Constants.arrowSize / 2)
}
}