Skip to content

Commit

Permalink
Merge pull request #97 from Nexters/feature/capture-image(#95)
Browse files Browse the repository at this point in the history
์—ฌ๋Ÿฌ๊ฐ€์ง€ ์ถ”๊ฐ€ ๋ฐ ๊ฐœ์„  ๐Ÿ™‡
  • Loading branch information
enebin authored Sep 19, 2023
2 parents f5d9653 + f70a4d5 commit 5bc23e6
Show file tree
Hide file tree
Showing 49 changed files with 1,421 additions and 433 deletions.
Binary file modified Encrypted/XCConfig/App/DEV.xcconfig.encrypted
Binary file not shown.
Binary file modified Encrypted/XCConfig/App/PROD.xcconfig.encrypted
Binary file not shown.
9 changes: 8 additions & 1 deletion Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public extension Project {
],
"API_BASE_URL": "$(API_BASE_URL)",
"KAKAO_API_KEY": "$(KAKAO_API_KEY)",
"BITLY_API_KEY": "$(BITLY_API_KEY)",
"UIUserInterfaceStyle": "Light",
"NSAppTransportSecurity": [
"NSExceptionDomains": [
Expand All @@ -33,7 +34,13 @@ public extension Project {
],
]
],
"LSApplicationQueriesSchemes": ["kakaokompassauth", "kakaolink"]
"LSApplicationQueriesSchemes": [
"kakaokompassauth",
"kakaolink",
"instagram",
"instagram-stories"
],
"NSPhotoLibraryAddUsageDescription": "์Šคํฌ๋ฆฐ์ƒท์„ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด์„œ ์•จ๋ฒ” ์ ‘๊ทผ ๊ถŒํ•œ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค"
]

static let baseUrlInfoPlist: [String: InfoPlist.Value] = [
Expand Down
33 changes: 33 additions & 0 deletions Projects/Core/Sources/Utility/UI/ImageSaver.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// ImageSaver.swift
// Core
//
// Created by Young Bin on 2023/09/10.
// Copyright ยฉ 2023 team.humanwave. All rights reserved.
//

import SwiftUI
import Photos

public class ImageSaver: NSObject {
private var completion: ((Error?) -> Void)?

public func save(_ image: UIImage, completion: @escaping (Error?) -> Void) {
UIImageWriteToSavedPhotosAlbum(image, self, #selector(saveError), nil)
self.completion = completion
}

@objc private func saveError(
_ image: UIImage,
didFinishSavingWithError error: Error?,
contextInfo: UnsafeRawPointer
) {
DispatchQueue.main.async {
if let error {
self.completion?(error)
} else {
self.completion?(nil)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import UIKit
class PinchZoomView: UIView {
weak var delegate: PinchZoomViewDelgate?

private var lastScale: CGFloat = 1

private(set) var scale: CGFloat = 0 {
didSet {
delegate?.pinchZoomView(self, didChangeScale: scale)
Expand Down Expand Up @@ -74,14 +76,15 @@ class PinchZoomView: UIView {
numberOfTouches = gesture.numberOfTouches
}

scale = gesture.scale
scale = lastScale * gesture.scale

location = gesture.location(in: self)
offset = CGSize(width: location.x - startLocation.x, height: location.y - startLocation.y)

case .ended, .cancelled, .failed:
isPinching = false
scale = 1.0
scale = scale.between(min: 0.5, max: 2.0)
lastScale = scale
anchor = .center
offset = .zero
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,45 @@ import SwiftUI

public extension View {
/// ํ™”๋ฉด์„ ๊ผฌ์ง‘์–ด์„œ ๋Š˜์˜€๋‹ค ์ค„์˜€๋‹ค ํ•ด๋ณด์„ธ์š”
///
/// ์™œ๋ƒ๋ฉด for fun. ``PinchZoomView`` ์ฐธ๊ณ 
func pinchZooming() -> some View {
self.modifier(PinchToZoomViewModifier())
}

func pinchZooming(with scale: Binding<CGFloat>) -> some View {
self.modifier(PinchToZoomGestureRecognizer(scale: scale))
}
}

struct PinchToZoomViewModifier: ViewModifier {
@State var scale: CGFloat = 1.0
@State var anchor: UnitPoint = .zero
@State var offset: CGSize = .zero
@State var isPinching: Bool = false

func body(content: Content) -> some View {
content
// .scaleEffect(scale, anchor: anchor)
.scaleEffect(scale) // Prevent glitching
.offset(offset)
.overlay(
PinchZoomViewRepresentable(
scale: $scale,
anchor: $anchor,
offset: $offset,
isPinching: $isPinching)
.opacity(0.1))
.animation(isPinching ? .none : .spring(), value: isPinching)
}
}

struct PinchToZoomGestureRecognizer: ViewModifier {
@Binding var scale: CGFloat
@State var anchor: UnitPoint = .center
@State var offset: CGSize = .zero
@State var isPinching: Bool = false

func body(content: Content) -> some View {
content
.scaleEffect(scale, anchor: anchor)
.offset(offset)
.overlay(
PinchZoomViewRepresentable(
Expand Down
26 changes: 26 additions & 0 deletions Projects/Core/Sources/Utility/UI/ReverseMask.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// ReverseMask.swift
// Core
//
// Created by Young Bin on 2023/09/10.
// Copyright ยฉ 2023 team.humanwave. All rights reserved.
//

import SwiftUI

public extension View {
@inlinable func reverseMask<Mask: View>(
alignment: Alignment = .center,
@ViewBuilder _ mask: () -> Mask
) -> some View {
self.mask(
ZStack(alignment: .center) {
Rectangle()

mask()
.blendMode(.destinationOut)
}
.compositingGroup()
)
}
}
88 changes: 88 additions & 0 deletions Projects/Core/Sources/Utility/UI/ScreenShooter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//
// ScreenShooter.swift
// Core
//
// Created by Young Bin on 2023/09/12.
// Copyright ยฉ 2023 team.humanwave. All rights reserved.
//

import SwiftUI

public struct Screenshotter<Content: View>: UIViewControllerRepresentable {
@Binding var isTakingScreenshot: Bool
var onScreenshotTaken: (UIImage?) -> Void
let content: () -> Content

public init(
isTakingScreenshot: Binding<Bool>,
@ViewBuilder content: @escaping () -> Content,
onScreenshotTaken: @escaping (UIImage?) -> Void
) {
self._isTakingScreenshot = isTakingScreenshot
self.content = content
self.onScreenshotTaken = onScreenshotTaken
}

public func makeUIViewController(context: Context) -> UIViewController {
let viewController = UIViewController()
let hostingController = UIHostingController(rootView: content())

viewController.addChild(hostingController)
viewController.view.addSubview(hostingController.view)
hostingController.view.frame = viewController.view.bounds
hostingController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]

context.coordinator.hostingController = hostingController // coordinator์— hostingController๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
return viewController
}

public func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
context.coordinator.hostingController?.rootView = content() // rootView๋ฅผ ์—…๋ฐ์ดํŠธ ํ•ฉ๋‹ˆ๋‹ค.

if isTakingScreenshot {
DispatchQueue.main.async {
let screenshot = self.takeScreenshot(of: uiViewController.view)
self.onScreenshotTaken(screenshot)
self.isTakingScreenshot = false
}
}
}

public func makeCoordinator() -> Coordinator {
return Coordinator()
}

public class Coordinator {
var hostingController: UIHostingController<Content>?
}

func takeScreenshot(of view: UIView) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
defer { UIGraphicsEndImageContext() }
view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
let snapshotImage = UIGraphicsGetImageFromCurrentImageContext()
return snapshotImage
}
}

// ์•„์ง ์“ธ ๋ฐ๋Š” ์—†์Œ
public extension View {
func capture() -> UIImage? {
let targetSize = UIScreen.main.bounds.size
return capture(targetSize: targetSize)
}

func capture(targetSize: CGSize) -> UIImage? {
let controller = UIHostingController(rootView: self)
let view = controller.view

view?.bounds = CGRect(origin: .zero, size: targetSize)
view?.backgroundColor = .clear
view?.layoutIfNeeded() // Force layout

let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
}
68 changes: 0 additions & 68 deletions Projects/Core/Sources/Utility/UI/WebView/WebViewWarmUper.swift

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "mypage_empty.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "[email protected]",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "[email protected]",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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 Projects/Domain/Sources/Model/CirclePack/CircleData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,28 @@ extension CircleData: Identifiable {
metadata.animationId
}
}


public extension Array where Element == CircleData {
func rotate(degree: Angle) -> [CircleData] {
func formula(xPoint: CGFloat, yPoint: CGFloat) -> (x: CGFloat, y: CGFloat) {
let degree = CGFloat(degree.degrees)
let newXPoint = xPoint * cos(degree) - yPoint * sin(degree)
let newYPoint = yPoint * cos(degree) + xPoint * sin(degree)

return (newXPoint, newYPoint)
}

return self.map { data in
let newCoordinate = formula(xPoint: data.xPoint, yPoint: data.yPoint)
let newCircle = CircleData(
color: data.color,
xPoint: newCoordinate.x,
yPoint: newCoordinate.y,
radius: data.radius,
metadata: data.metadata)

return newCircle
}
}
}
2 changes: 1 addition & 1 deletion Projects/Domain/Sources/Model/KeymeWebModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ import Foundation

public struct KeymeWebViewModel: Codable, Equatable {
public let matchRate: Float
public let resultCode: String
public let resultCode: String?
public let testResultId: Int
}
Loading

0 comments on commit 5bc23e6

Please sign in to comment.