Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jasudev committed Jan 28, 2022
1 parent 235412c commit 896d110
Show file tree
Hide file tree
Showing 21 changed files with 1,258 additions and 1 deletion.
Binary file added Markdown/Zoomable.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"object": {
"pins": [
{
"package": "SDWebImage",
"repositoryURL": "https://github.com/SDWebImage/SDWebImage.git",
"state": {
"branch": null,
"revision": "2c53f531f1bedd253f55d85105409c28ed4a922c",
"version": "5.12.3"
}
},
{
"package": "SDWebImageSwiftUI",
"repositoryURL": "https://github.com/SDWebImage/SDWebImageSwiftUI.git",
"state": {
"branch": null,
"revision": "cd8625b7cf11a97698e180d28bb7d5d357196678",
"version": "2.0.2"
}
}
]
},
"version": 1
}
32 changes: 32 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "Zoomable",
platforms: [
.iOS(.v13),
.macOS(.v11)
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "Zoomable",
targets: ["Zoomable"]),
],
dependencies: [
.package(url: "https://github.com/SDWebImage/SDWebImage.git", .branch("master")),
.package(url: "https://github.com/SDWebImage/SDWebImageSwiftUI.git", .branch("master"))
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "Zoomable",
dependencies: ["SDWebImage", "SDWebImageSwiftUI"]),
.testTarget(
name: "ZoomableTests",
dependencies: ["Zoomable"]),
]
)
52 changes: 51 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,51 @@
# Zoomable
# **Zoomable**
It is a container that allows you to zoom in and out of an image using only SwiftUI.

[![Platforms](https://img.shields.io/badge/Platforms-iOS%20%7C%20macOS-blue?style=flat-square)](https://developer.apple.com/macOS)
[![iOS](https://img.shields.io/badge/iOS-13.0-blue.svg)](https://developer.apple.com/iOS)
[![macOS](https://img.shields.io/badge/macOS-11.0-blue.svg)](https://developer.apple.com/macOS)
[![instagram](https://img.shields.io/badge/instagram-@dev.fabula-orange.svg?style=flat-square)](https://www.instagram.com/dev.fabula)
[![SPM](https://img.shields.io/badge/SPM-compatible-red?style=flat-square)](https://developer.apple.com/documentation/swift_packages/package/)
[![MIT](https://img.shields.io/badge/licenses-MIT-red.svg)](https://opensource.org/licenses/MIT)

## Screenshot
<img src="Markdown/Zoomable.gif">

## Example Usages
1. ZoomableView
```swift
ZoomableView(size: CGSize(width: 300, height: 300), min: 1.0, max: 6.0, showsIndicators: true) {
Image("image")
.resizable()
.scaledToFit()
.background(Color.black)
.clipped()
}
```

3. ZoomableImageView
```swift
ZoomableImageView(url: url, min: 1.0, max: 3.0, showsIndicators: true) {
Text("ZoomableImageView")
.padding()
.background(Color.black.opacity(0.5))
.cornerRadius(8)
.foregroundColor(Color.white)
}
```

## Dependencies
```
dependencies: [
.package(url: "https://github.com/SDWebImage/SDWebImage.git", .branch("master")),
.package(url: "https://github.com/SDWebImage/SDWebImageSwiftUI.git", .branch("master"))
]
```

## Contact
instagram : [@dev.fabula](https://www.instagram.com/dev.fabula)
email : [dev.fabula@gmail.com](mailto:dev.fabula@gmail.com)

## License
ZoomableView is available under the MIT license. See the [LICENSE](LICENSE) file for more info.
76 changes: 76 additions & 0 deletions Sources/Zoomable/ZoomableImageView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//
// ZoomableImageView.swift
//
//
// Created by jasu on 2022/01/27.
// Copyright (c) 2022 jasu All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is furnished
// to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

import SwiftUI
import SDWebImageSwiftUI

public struct ZoomableImageView<Content>: View where Content: View {

private var url: URL
private var min: CGFloat = 1.0
private var max: CGFloat = 3.0
private var showsIndicators: Bool = false
@ViewBuilder private var overlay: () -> Content

@State private var imageSize: CGSize = .zero

/**
Initializes an `ZoomableImageView`
- parameter url : The image url.
- parameter min : The minimum value that can be zoom out.
- parameter max : The maximum value that can be zoom in.
- parameter showsIndicators : A value that indicates whether the scroll view displays the scrollable component of the content offset, in a way that’s suitable for the platform.
- parameter overlay : The ZoomableImageView view’s overlay.
*/
public init(url: URL,
min: CGFloat = 1.0,
max: CGFloat = 3.0,
showsIndicators: Bool = false,
@ViewBuilder overlay: @escaping () -> Content) {
self.url = url
self.min = min
self.max = max
self.showsIndicators = showsIndicators
self.overlay = overlay
}

public var body: some View {
GeometryReader { proxy in
ZoomableView(size: imageSize, min: self.min, max: self.max, showsIndicators: self.showsIndicators) {
WebImage(url: url)
.resizable()
.onSuccess(perform: { image, _, _ in
DispatchQueue.main.async {
self.imageSize = CGSize(width: proxy.size.width, height: proxy.size.width * (image.size.height / image.size.width))
}
})
.indicator(.activity)
.scaledToFit()
.clipShape(Rectangle())
.overlay(self.overlay())
}
}
}
}
103 changes: 103 additions & 0 deletions Sources/Zoomable/ZoomableModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//
// ZoomableModifier.swift
// ZoomableView
//
// Created by jasu on 2022/01/26.
// Copyright (c) 2022 jasu All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is furnished
// to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

import SwiftUI

public struct ZoomableModifier: ViewModifier {

private enum ZoomState {
case inactive
case active(scale: CGFloat)

var scale: CGFloat {
switch self {
case .active(let scale):
return scale
default: return 1.0
}
}
}

private var contentSize: CGSize
private var min: CGFloat = 1.0
private var max: CGFloat = 3.0
private var showsIndicators: Bool = false

@GestureState private var zoomState = ZoomState.inactive
@State private var currentScale: CGFloat = 1.0

/**
Initializes an `ZoomableModifier`
- parameter contentSize : The content size of the views.
- parameter min : The minimum value that can be zoom out.
- parameter max : The maximum value that can be zoom in.
- parameter showsIndicators : A value that indicates whether the scroll view displays the scrollable component of the content offset, in a way that’s suitable for the platform.
*/
public init(contentSize: CGSize,
min: CGFloat = 1.0,
max: CGFloat = 3.0,
showsIndicators: Bool = false) {
self.contentSize = contentSize
self.min = min
self.max = max
self.showsIndicators = showsIndicators
}

var scale: CGFloat {
return currentScale * zoomState.scale
}

var zoomGesture: some Gesture {
MagnificationGesture()
.updating($zoomState) { value, state, transaction in
state = .active(scale: value)
}
.onEnded { value in
var new = self.currentScale * value
if new <= min { new = min }
if new >= max { new = max }
self.currentScale = new
}
}

var doubleTapGesture: some Gesture {
TapGesture(count: 2).onEnded {
if scale <= min { currentScale = max } else
if scale >= max { currentScale = min } else {
currentScale = ((max - min) * 0.5 + min) < scale ? max : min
}
}
}

public func body(content: Content) -> some View {
ScrollView([.horizontal, .vertical], showsIndicators: showsIndicators) {
content
.frame(width: contentSize.width * scale, height: contentSize.height * scale, alignment: .center)
.scaleEffect(scale, anchor: .center)
}
.gesture(ExclusiveGesture(zoomGesture, doubleTapGesture))
.animation(.easeInOut, value: scale)
}
}
62 changes: 62 additions & 0 deletions Sources/Zoomable/ZoomableView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// ZoomableView.swift
// ZoomableView
//
// Created by jasu on 2022/01/25.
// Copyright (c) 2022 jasu All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is furnished
// to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

import SwiftUI

public struct ZoomableView<Content>: View where Content: View {

private var size: CGSize
private var min: CGFloat = 1.0
private var max: CGFloat = 3.0
private var showsIndicators: Bool = false
@ViewBuilder private var content: () -> Content

/**
Initializes an `ZoomableView`
- parameter size : The content size of the views.
- parameter min : The minimum value that can be zoom out.
- parameter max : The maximum value that can be zoom in.
- parameter showsIndicators : A value that indicates whether the scroll view displays the scrollable component of the content offset, in a way that’s suitable for the platform.
- parameter content : The ZoomableView view’s content.
*/
public init(size: CGSize,
min: CGFloat = 1.0,
max: CGFloat = 3.0,
showsIndicators: Bool = false,
@ViewBuilder content: @escaping () -> Content) {
self.size = size
self.min = min
self.max = max
self.showsIndicators = showsIndicators
self.content = content
}

public var body: some View {
content()
.frame(width: size.width, height: size.height, alignment: .center)
.contentShape(Rectangle())
.modifier(ZoomableModifier(contentSize: self.size, min: min, max: max, showsIndicators: showsIndicators))
}
}
11 changes: 11 additions & 0 deletions Tests/ZoomableTests/ZoomableTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import XCTest
@testable import Zoomable

final class ZoomableTests: XCTestCase {
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
XCTAssertEqual(Zoomable().text, "Hello, World!")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading

0 comments on commit 896d110

Please sign in to comment.