Restructured everything

This commit is contained in:
Muhand Jumah 2023-04-29 18:03:10 -04:00
parent 903a063257
commit bbbfbcb7c5
17 changed files with 915 additions and 550 deletions

View File

@ -18,8 +18,7 @@ public extension View {
/// - chunk: The `FlowChunk` associated with the current view, which determines the spotlight shape, background, and other properties.
///
/// - Returns: The modified view with the matched geometry effect applied, allowing it to be highlighted by the spotlight effect.
func configureChunkForSpotlight<Chunk: FlowChunk>(parentIdentifier: String, chunk: Chunk) -> some View {
let namespace = getNamespace(from: parentIdentifier)
func configureChunkForSpotlight<Chunk: FlowChunk>(namespace: Namespace.ID, parentIdentifier: String, chunk: Chunk) -> some View {
return self
.matchedGeometryEffect(
@ -42,22 +41,19 @@ public extension View {
/// - chunks: An array of `FlowChunk` instances associated with the child views to be highlighted by the spotlight effect.
///
/// - Returns: The modified view with the `HighlightingView` applied, containing the spotlight effect and its child views.
func assembleSpotlightChunks<Chunk: FlowChunk>(uniqueIdentifier: String, chunks: [Chunk], showTutorial: Binding<Bool>) -> some View {
let namespace = getNamespace(from: uniqueIdentifier)
func assembleSpotlightChunks<Chunk: FlowChunk>(namespace: Namespace.ID, uniqueIdentifier: String, chunks: [Chunk], showTutorial: Binding<Bool>, _ onStepChange: ((_ chunk: Chunk) -> ())? = nil) -> some View {
return HighlightingView(
namespace: namespace,
showTutorial: showTutorial,
chunks: chunks
) {
self
}
}
return (
HighlightingView(
namespace: namespace,
showTutorial: showTutorial,
chunks: chunks
) {
self
}
.onStepChange(onStepChange ?? { _ in })
)
func convertToInstructionsView(position: InstructionsViewPosition) -> InstructionsView {
InstructionsView(position: position) {
self
}
}
}
@ -70,17 +66,3 @@ public extension View {
)
}
}
// MARK: - Helper Methods
fileprivate extension View {
func getNamespace(from identifier: String) -> Namespace.ID {
if let existingNamespace = FutureFlowManager.currentNamespaces[identifier] {
return existingNamespace
} else {
let newNamespace = Namespace().wrappedValue
FutureFlowManager.currentNamespaces[identifier] = newNamespace
return newNamespace
}
}
}

View File

@ -11,7 +11,12 @@ public protocol FlowChunk: Hashable, Identifiable {
var spotlightShape: SpotlightShape { get }
var spotlightBackground: SpotlightBackground { get }
var instructionsViewType: InstructionsViewType? { get }
@InstructionsViewBuilder func instructionsView(
_ next: @escaping () -> (),
_ back: @escaping () -> ()
) -> AnyInstructionsView?
var instructionsViewPosition: InstructionsViewPosition { get }
}
public extension FlowChunk {
@ -27,7 +32,10 @@ public extension FlowChunk {
return .black
}
var instructionsViewType: InstructionsViewType? {
return nil
func instructionsView(
_ next: @escaping () -> (),
_ back: @escaping () -> ()
) -> AnyInstructionsView? {
nil
}
}

View File

@ -1,12 +0,0 @@
//
// FutureFlowManager.swift
//
//
// Created by Muhand Jumah on 4/23/23.
//
import SwiftUI
internal class FutureFlowManager {
internal static var currentNamespaces: [String:Namespace.ID] = [:]
}

View File

@ -7,14 +7,21 @@
import SwiftUI
public final class Manager<Chunk: FlowChunk>: ObservableObject {
@Published var totalChunks: [Chunk] = []
@Published var currentStepIndex: Int = 0
}
internal struct HighlightingView<Content: View, Chunk: FlowChunk>: View {
@StateObject var manager: Manager<Chunk> = .init()
// Binding variables
@Binding var showTutorial: Bool
// Private variables
let namespace: Namespace.ID
let chunks: [Chunk]
let content: Content
let content: () -> Content
#if DEBUG
// State variables (TODO: Delete)
@ -23,62 +30,65 @@ internal struct HighlightingView<Content: View, Chunk: FlowChunk>: View {
@Namespace private var namespace2
internal init(namespace: Namespace.ID, showTutorial: Binding<Bool>, chunks: [Chunk], @ViewBuilder content: () -> Content) {
private var onCurrentChunkChanged: ((_ newChunk: Chunk) -> ())?
internal init(
namespace: Namespace.ID,
showTutorial: Binding<Bool>,
chunks: [Chunk],
@ViewBuilder content: @escaping () -> Content)
{
self.namespace = namespace
self._showTutorial = showTutorial
self.chunks = chunks
self.content = content()
self.onCurrentChunkChanged = nil
self.content = content
}
fileprivate init(
namespace: Namespace.ID,
showTutorial: Binding<Bool>,
chunks: [Chunk],
onCurrentChunkChanged: @escaping (_ chunk: Chunk) -> (),
content: @escaping () -> Content)
{
self.namespace = namespace
self._showTutorial = showTutorial
self.chunks = chunks
self.onCurrentChunkChanged = onCurrentChunkChanged
self.content = content
}
internal var body: some View {
ZStack {
self.content
self.content()
ZStack {
if (self.showTutorial) {
let chunk = self.chunks[self.currentIndex]
SpotlightView(
chunk: self.chunks[self.currentIndex],
chunk: chunk,
namespace: self.namespace,
namespace2: self.namespace2
)
.overlay(
GeometryReader { reader in
instructionsOverlay(reader: reader)
.animation(.easeOut, value: self.showTutorial ? self.currentIndex : nil)
// Text("\(reader.size.width) X \(reader.size.height)")
// ZStack {
// Color.blue
// }
}
.matchedGeometryEffect(id: 10000, in: self.namespace2, isSource: false)
// .overlay(
// GeometryReader { reader in
// Text("\(reader.size.width) X \(reader.size.height)")
// }
// )
)
.opacity(self.showTutorial ? 1 : 0)
.animation(
.easeOut,
value: self.showTutorial ? self.currentIndex : nil
)
.opacity(self.showTutorial ? 1 : 0)
.animation(.easeOut, value: self.showTutorial ? self.currentIndex : nil)
chunk.instructionsView(self.advance, self.previous)
.frame(width:UIScreen.main.bounds.width * 0.8)
.offset(x:0, y:self.chunks[self.currentIndex].instructionsViewPosition == .below ? 20 : -20)
.matchedGeometryEffect(id: 10001,
in: self.namespace2,
properties: .position,
anchor: self.chunks[self.currentIndex].instructionsViewPosition == .below ? .top : .bottom,
isSource: false)
.animation(.easeOut, value: self.showTutorial ? self.currentIndex : nil)
}
}
.transition(.opacity)
.animation(.linear, value: self.showTutorial)
//#if DEBUG
// Button(action: {
// if self.currentIndex < self.chunks.count - 1{
// self.currentIndex += 1
// } else {
// self.currentIndex = 0
// }
// }) {
// Text("NEXT")
// }
//#endif
}
.onChange(of: self.showTutorial) { newValue in
if(newValue == false) {
@ -87,6 +97,19 @@ internal struct HighlightingView<Content: View, Chunk: FlowChunk>: View {
}
}
}
.onChange(of: self.currentIndex) { newValue in
if let callback = self.onCurrentChunkChanged {
callback(self.chunks[newValue])
}
}
}
func onStepChange(_ callback: @escaping (_ chunk: Chunk) -> ()) -> HighlightingView {
.init(namespace: self.namespace,
showTutorial: self.$showTutorial,
chunks: self.chunks,
onCurrentChunkChanged: callback,
content: self.content)
}
}
@ -97,9 +120,7 @@ private extension HighlightingView {
return
}
// withAnimation(.easeOut) {
self.showTutorial = false
// }
self.showTutorial = false
}
func previous() {
@ -107,88 +128,4 @@ private extension HighlightingView {
self.currentIndex -= 1
}
}
@ViewBuilder func instructionsOverlay(reader: GeometryProxy) -> some View {
if let instructionsViewType = self.chunks[self.currentIndex].instructionsViewType {
let v = instructionsViewType
.body(
currentIndex: self.$currentIndex,
maxCount: self.chunks.count,
advance: self.advance,
previous: self.previous
)
ZStack {
// TODO: Find a better way than this hacky method (this is needed for now because we need the `v`'s frame in overlay.
v
.opacity(0)
}
.overlay(
GeometryReader { rr in
v
.frame(width: UIScreen.main.bounds.size.width)
.offset(
x: -(UIScreen.main.bounds.size.width - reader.size.width) / 2,
y: self.getYOffset(position: v.position, in: v.position == .below ? reader.size : rr.size)
)
}
.opacity(self.showTutorial ? 1.0 : 0.0)
)
// v
// .overlay(
// Text("\(reader.size.height)").allowsHitTesting(false))
// .offset(
// x: -(UIScreen.main.bounds.size.width - reader.size.width) / 2,
// y: self.getYOffset(position: v.position, in: reader.size)
// )
// y: self.getYOffset(position: v.position, in: reader.size)
// )
}
// ZStack {
// if let sle
// if(self.chunks[self.currentIndex].instruction != nil) {
// SimpleInstructionsBubble(text: "TEST")
// .frame(width: 200, height: 100, alignment: .center)
// self.chunks[self.currentIndex].instruction!.bubble.body(instruction: self.chunks[self.currentIndex].instruction!)
// }
// .offset(x: 0, y: reader.size.height + 20)
// if let instruction = self.chunks[self.currentIndex].instruction {
// self
// .chunks[self.currentIndex]
// .instructionsViewType!
//// .inWithEnum()
// .body(adv: self.advance, prev: self.previous)
// .instructionsView(advance: self.advance, previous: self.previous)
// instruction.bubble.body(instruction: instruction)
// .padding(.horizontal, 15)
// .frame(width: UIScreen.main.bounds.size.width)
//// .offset(x: 0, y: 400)
// .offset(
// x: -(UIScreen.main.bounds.size.width - reader.size.width) / 2,
// y: self.getYOffset(position: instruction.position, in: reader.size)
// )
// }
// if (self.chunk.instructionsBubble != nil) {
// self.chunk.instructionsBubble!.body
// .padding(.horizontal)
// .frame(width: UIScreen.main.bounds.size.width)
// .offset(x: 0, y: self.getYOffset(position: self.chunk.instructionsBubble!.position, in: reader.size))
// }
// }
}
func getYOffset(position: InstructionsViewPosition, in frame: CGSize) -> CGFloat {
switch position {
case .above:
return -(frame.height + 20)
default:
return (frame.height + 20)
}
}
}

View File

@ -0,0 +1,23 @@
//
// AnyInstructionsView.swift
//
//
// Created by Muhand Jumah on 4/29/23.
//
import SwiftUI
public struct AnyInstructionsView: InstructionsView {
private let _view: () -> AnyView
public var body: some View {
_view()
}
public init<I: InstructionsView>(_ instructionsView: I) {
_view = {
instructionsView
.eraseToAnyView()
}
}
}

View File

@ -7,21 +7,13 @@
import SwiftUI
public struct InstructionsView : View {
internal let position: InstructionsViewPosition
private let content: AnyView
public protocol InstructionsView: View {
}
internal init<Content: View>
(
position: InstructionsViewPosition = .below,
@ViewBuilder _ content: () -> Content
) {
self.position = position
self.content = content().eraseToAnyView()
}
@_spi(Private)
public var body: some View {
self.content
extension InstructionsView {
func eraseToAnyInstructionsView() -> AnyInstructionsView {
AnyInstructionsView(
self
)
}
}

View File

@ -0,0 +1,26 @@
//
// InstructionsViewBuilder.swift
//
//
// Created by Muhand Jumah on 4/29/23.
//
import Foundation
@resultBuilder
public struct InstructionsViewBuilder {
public static func buildBlock<Content: InstructionsView>(_ instructionsView: Content?) -> AnyInstructionsView? {
instructionsView?
.eraseToAnyInstructionsView()
}
public static func buildEither<Content: InstructionsView>(first component: Content?) -> AnyInstructionsView? {
component?
.eraseToAnyInstructionsView()
}
public static func buildEither<Content: InstructionsView>(second component: Content?) -> AnyInstructionsView? {
component?
.eraseToAnyInstructionsView()
}
}

View File

@ -1,127 +1,128 @@
////
//// InstructionsViewType.swift
////
////
//// Created by Muhand Jumah on 4/26/23.
////
//
// InstructionsViewType.swift
//
//
// Created by Muhand Jumah on 4/26/23.
//
public enum InstructionsViewType {
case simple(instructions: [String], position: InstructionsViewPosition = .below)
case custom(view: (_ advance: @escaping () -> (), _ previous: @escaping () -> ()) -> InstructionsView)
internal func body(
currentIndex: Binding<Int>,
maxCount: Int,
advance: @escaping () -> (),
previous: @escaping () -> ()
) -> InstructionsView {
switch self {
case .simple(let instructions, let position):
return SimpleInstructionsViewWrapper(currentGlobalIndex: currentIndex,
globalMaxCount: maxCount,
advance: advance,
previous: previous,
instructions: instructions)
.convertToInstructionsView(position: position)
case .custom(let view):
return view(advance, previous)
}
}
}
// TODO: Rework this piece
import SwiftUI
internal struct SimpleInstructionsViewWrapper: View {
// Global instructions
@Binding var currentGlobalIndex: Int
var globalMaxCount: Int
let advance: () -> ()
let previous: () -> ()
private(set) var instructions: [String]
@State private var currentInstructionIndex: Int = 0
@State private var showPrevious: Bool = false
@State private var showNext: Bool = false
var body: some View {
SimpleInstructionsView(
text: self.instructions[self.currentInstructionIndex],
showPrevious: .init (
get: {
return (self.currentGlobalIndex > 0 && self.globalMaxCount > 0) || (self.currentInstructionIndex > 0 && self.instructions.count > 0)
},
set: { _ in}
),
showNext: .init(
get: {
return self.currentInstructionIndex < instructions.count || self.currentGlobalIndex < self.globalMaxCount
},
set: { _ in }
)
)
.onTapNext {
guard currentInstructionIndex == instructions.count - 1 else {
currentInstructionIndex += 1
return
}
advance()
self.currentInstructionIndex = 0
}
.onTapPrev {
guard currentInstructionIndex == 0 else {
currentInstructionIndex -= 1
return
}
// guard self.currentInstructionIndex > 0 else {
// return
// }
// self.currentInstructionIndex -= 1
self.currentInstructionIndex = 0
previous()
// self.currentInstructionIndex = instructions.count
}
.padding(.horizontal, 15)
// .onChange(of: self.currentInstructionIndex, perform: { newValue in
// // For the next button
// if(newValue < instructions.count - 1 || currentGlobalIndex < globalMaxCount - 1) {
// self.showNext = true
// } else {
// self.showNext = false
// }
//
// if(newValue > 0 && self.instructions.count > 0) {
// self.showPrevious = true
// } else {
// self.showPrevious = false
// }
// })
.onAppear {
// Next
if(currentInstructionIndex < instructions.count - 1 || currentGlobalIndex < globalMaxCount) {
self.showNext = true
} else {
self.showNext = false
}
// if(currentInstructionIndex < instructions.count - 1) {
// self.showNext = true
// } else {
// self.showNext = false
// }
//
if(currentInstructionIndex > 0 && self.instructions.count > 0) {
self.showPrevious = true
} else {
self.showPrevious = false
}
}
}
}
//struct SimpleInstructionsViewWrapper_Previews: PreviewProvider {
// static var previews: some View {
// SimpleInstructionsViewWrapper(instructions: ["Hello", "World", "askdlfjalk shfjlka dhfkj ahsdfkhdj"])
// }
//public enum InstructionsViewType {
//// case simple(instructions: [String], position: InstructionsViewPosition = .below)
//// case custom(view: (_ advance: @escaping () -> (), _ previous: @escaping () -> ()) -> InstructionsView)
// case custom(view:(_ advance: @escaping () -> (), _ previous: @escaping () -> ()) -> AnyInstructionsView)
////
//// internal func body(
//// currentIndex: Binding<Int>,
//// maxCount: Int,
//// advance: @escaping () -> (),
//// previous: @escaping () -> ()
//// ) -> InstructionsView {
//// switch self {
//// case .simple(let instructions, let position):
//// return SimpleInstructionsViewWrapper(currentGlobalIndex: currentIndex,
//// globalMaxCount: maxCount,
//// advance: advance,
//// previous: previous,
//// instructions: instructions)
//// .convertToInstructionsView(position: position)
//// case .custom(let view):
//// return view(advance, previous)
//// }
//// }
//}
//
////// TODO: Rework this piece
////import SwiftUI
////
////internal struct SimpleInstructionsViewWrapper: View {
//// // Global instructions
//// @Binding var currentGlobalIndex: Int
//// var globalMaxCount: Int
//// let advance: () -> ()
//// let previous: () -> ()
////
//// private(set) var instructions: [String]
//// @State private var currentInstructionIndex: Int = 0
//// @State private var showPrevious: Bool = false
//// @State private var showNext: Bool = false
////
//// var body: some View {
//// SimpleInstructionsView(
//// text: self.instructions[self.currentInstructionIndex],
//// showPrevious: .init (
//// get: {
//// return (self.currentGlobalIndex > 0 && self.globalMaxCount > 0) || (self.currentInstructionIndex > 0 && self.instructions.count > 0)
//// },
//// set: { _ in}
//// ),
//// showNext: .init(
//// get: {
//// return self.currentInstructionIndex < instructions.count || self.currentGlobalIndex < self.globalMaxCount
//// },
//// set: { _ in }
//// )
//// )
//// .onTapNext {
//// guard currentInstructionIndex == instructions.count - 1 else {
//// currentInstructionIndex += 1
//// return
//// }
////
//// advance()
//// self.currentInstructionIndex = 0
//// }
//// .onTapPrev {
//// guard currentInstructionIndex == 0 else {
//// currentInstructionIndex -= 1
//// return
//// }
////// guard self.currentInstructionIndex > 0 else {
////// return
////// }
////// self.currentInstructionIndex -= 1
//// self.currentInstructionIndex = 0
//// previous()
////// self.currentInstructionIndex = instructions.count
////
//// }
//// .padding(.horizontal, 15)
////// .onChange(of: self.currentInstructionIndex, perform: { newValue in
////// // For the next button
////// if(newValue < instructions.count - 1 || currentGlobalIndex < globalMaxCount - 1) {
////// self.showNext = true
////// } else {
////// self.showNext = false
////// }
//////
////// if(newValue > 0 && self.instructions.count > 0) {
////// self.showPrevious = true
////// } else {
////// self.showPrevious = false
////// }
////// })
//// .onAppear {
//// // Next
//// if(currentInstructionIndex < instructions.count - 1 || currentGlobalIndex < globalMaxCount) {
//// self.showNext = true
//// } else {
//// self.showNext = false
//// }
////// if(currentInstructionIndex < instructions.count - 1) {
////// self.showNext = true
////// } else {
////// self.showNext = false
////// }
//////
//// if(currentInstructionIndex > 0 && self.instructions.count > 0) {
//// self.showPrevious = true
//// } else {
//// self.showPrevious = false
//// }
//// }
//// }
////}
////
//////struct SimpleInstructionsViewWrapper_Previews: PreviewProvider {
////// static var previews: some View {
////// SimpleInstructionsViewWrapper(instructions: ["Hello", "World", "askdlfjalk shfjlka dhfkj ahsdfkhdj"])
////// }
//////}

View File

@ -1,146 +1,147 @@
// TODO: Create a default view
////
//// SimpleInstructionsView.swift
////
////
//// Created by Muhand Jumah on 4/23/23.
////
//
// SimpleInstructionsView.swift
//
//import SwiftUI
//
// Created by Muhand Jumah on 4/23/23.
//struct SimpleInstructionsView: View {
// private var text: String
// private var nextTapped: () -> ()
// private var prevTapped: () -> ()
//
import SwiftUI
struct SimpleInstructionsView: View {
private var text: String
private var nextTapped: () -> ()
private var prevTapped: () -> ()
@Binding var showPrevious: Bool
@Binding var showNext: Bool
public init(
text: String,
showPrevious: Binding<Bool> = .constant(false),
showNext: Binding<Bool> = .constant(false)
) {
self.text = text
self.nextTapped = { }
self.prevTapped = { }
self._showPrevious = showPrevious
self._showNext = showNext
}
fileprivate init(
text: String,
nextTapped: @escaping () -> (),
prevTapped: @escaping () -> (),
showPrevious: Binding<Bool> = .constant(false),
showNext: Binding<Bool> = .constant(false)
) {
self.text = text
self.nextTapped = nextTapped
self.prevTapped = prevTapped
self._showPrevious = showPrevious
self._showNext = showNext
}
var body: some View {
// Color.purple
VStack(alignment:.leading, spacing: 20) {
HStack(alignment: .top, spacing: 10) {
Image(systemName: "lightbulb.circle.fill")
.font(.system(size: 32))
.foregroundColor(.blue)
Text(self.text)
.font(.body)
}
if (self.showPrevious || self.showNext) {
HStack(alignment: .center, spacing:15) {
Spacer()
if(self.showPrevious) {
Button(action: {
self.prevTapped()
}) {
Text("Previous")
.font(.callout)
.fontWeight(.medium)
.foregroundColor(.blue)
}
}
if(self.showNext) {
Button(action: {
self.nextTapped()
}) {
Text("Next")
.font(.callout)
.fontWeight(.semibold)
.padding(.horizontal, 10)
.padding(.vertical, 5)
.background(Color.blue)
.cornerRadius(3)
.foregroundColor(.white)
}
}
}
}
}
.padding(.horizontal, 20)
.padding(.vertical, 15)
.background(Color.white)
.cornerRadius(10)
.shadow(color: .black.opacity(0.2), radius: 5, x: 0, y: 0)
}
func onTapNext(_ callback: @escaping () -> ()) -> Self {
return .init(
text: self.text,
nextTapped: callback,
prevTapped: self.prevTapped,
showPrevious: self.$showPrevious,
showNext: self.$showNext
)
}
func onTapPrev(_ callback: @escaping () -> ()) -> Self {
return .init(
text: self.text,
nextTapped: self.nextTapped,
prevTapped: callback,
showPrevious: self.$showPrevious,
showNext: self.$showNext
)
}
}
struct SimpleInstructionsView_Previews: PreviewProvider {
static var previews: some View {
ZStack {
Color.white
.edgesIgnoringSafeArea(.all)
VStack(spacing: 5) {
SimpleInstructionsView(text: "This is just a tutorial asjdkfhla sdfhlakjsd hflkajsd hflkjasdhf lkjasdhf kljasd lajsdfh lkjsdhf lakjsdhf akjsl hfakjsd hadffajsdkfhlkajs hflkjas hfklja hsdjkfl haljskd fhljasdfhlaksd hflas dhfklj asdh fljasdlfjsdhfl.")
SimpleInstructionsView(text: "This is just a tutorial asjdkfhla sdfhlakjsd hflkajsd hflkjasdhf lkjasdhf kljasd lajsdfh lkjsdhf lakjsdhf akjsl hfakjsd hadffajsdkfhlkajs hflkjas hfklja hsdjkfl haljskd fhljasdfhlaksd hflas dhfklj asdh fljasdlfjsdhfl.", showNext: .constant(true))
.onTapNext {
print("Next Tapped")
}
SimpleInstructionsView(text: "This is just a tutorial asjdkfhla sdfhlakjsd hflkajsd hflkjasdhf lkjasdhf kljasd lajsdfh lkjsdhf lakjsdhf akjsl hfakjsd hadffajsdkfhlkajs hflkjas hfklja hsdjkfl haljskd fhljasdfhlaksd hflas dhfklj asdh fljasdlfjsdhfl.", showPrevious: .constant(true))
.onTapPrev {
print("Previous Tapped")
}
SimpleInstructionsView(text: "This is just a tutorial asjdkfhla sdfhlakjsd hflkajsd hflkjasdhf lkjasdhf kljasd lajsdfh lkjsdhf lakjsdhf akjsl hfakjsd hadffajsdkfhlkajs hflkjas hfklja hsdjkfl haljskd fhljasdfhlaksd hflas dhfklj asdh fljasdlfjsdhfl.", showPrevious: .constant(true), showNext: .constant(true))
.onTapNext {
print("Next Tapped")
}
.onTapPrev {
print("Previous Tapped")
}
}
.padding()
}
}
}
// @Binding var showPrevious: Bool
// @Binding var showNext: Bool
//
// public init(
// text: String,
// showPrevious: Binding<Bool> = .constant(false),
// showNext: Binding<Bool> = .constant(false)
// ) {
// self.text = text
// self.nextTapped = { }
// self.prevTapped = { }
// self._showPrevious = showPrevious
// self._showNext = showNext
// }
//
// fileprivate init(
// text: String,
// nextTapped: @escaping () -> (),
// prevTapped: @escaping () -> (),
// showPrevious: Binding<Bool> = .constant(false),
// showNext: Binding<Bool> = .constant(false)
// ) {
// self.text = text
// self.nextTapped = nextTapped
// self.prevTapped = prevTapped
// self._showPrevious = showPrevious
// self._showNext = showNext
// }
//
// var body: some View {
//// Color.purple
// VStack(alignment:.leading, spacing: 20) {
// HStack(alignment: .top, spacing: 10) {
// Image(systemName: "lightbulb.circle.fill")
// .font(.system(size: 32))
// .foregroundColor(.blue)
//
// Text(self.text)
// .font(.body)
// }
//
// if (self.showPrevious || self.showNext) {
// HStack(alignment: .center, spacing:15) {
// Spacer()
// if(self.showPrevious) {
// Button(action: {
// self.prevTapped()
// }) {
// Text("Previous")
// .font(.callout)
// .fontWeight(.medium)
// .foregroundColor(.blue)
// }
// }
//
// if(self.showNext) {
//
// Button(action: {
// self.nextTapped()
// }) {
// Text("Next")
// .font(.callout)
// .fontWeight(.semibold)
// .padding(.horizontal, 10)
// .padding(.vertical, 5)
// .background(Color.blue)
// .cornerRadius(3)
// .foregroundColor(.white)
// }
// }
// }
// }
// }
// .padding(.horizontal, 20)
// .padding(.vertical, 15)
// .background(Color.white)
// .cornerRadius(10)
// .shadow(color: .black.opacity(0.2), radius: 5, x: 0, y: 0)
// }
//
// func onTapNext(_ callback: @escaping () -> ()) -> Self {
// return .init(
// text: self.text,
// nextTapped: callback,
// prevTapped: self.prevTapped,
// showPrevious: self.$showPrevious,
// showNext: self.$showNext
// )
// }
//
// func onTapPrev(_ callback: @escaping () -> ()) -> Self {
// return .init(
// text: self.text,
// nextTapped: self.nextTapped,
// prevTapped: callback,
// showPrevious: self.$showPrevious,
// showNext: self.$showNext
// )
// }
//}
//
//struct SimpleInstructionsView_Previews: PreviewProvider {
// static var previews: some View {
// ZStack {
// Color.white
// .edgesIgnoringSafeArea(.all)
//
// VStack(spacing: 5) {
// SimpleInstructionsView(text: "This is just a tutorial asjdkfhla sdfhlakjsd hflkajsd hflkjasdhf lkjasdhf kljasd lajsdfh lkjsdhf lakjsdhf akjsl hfakjsd hadffajsdkfhlkajs hflkjas hfklja hsdjkfl haljskd fhljasdfhlaksd hflas dhfklj asdh fljasdlfjsdhfl.")
//
// SimpleInstructionsView(text: "This is just a tutorial asjdkfhla sdfhlakjsd hflkajsd hflkjasdhf lkjasdhf kljasd lajsdfh lkjsdhf lakjsdhf akjsl hfakjsd hadffajsdkfhlkajs hflkjas hfklja hsdjkfl haljskd fhljasdfhlaksd hflas dhfklj asdh fljasdlfjsdhfl.", showNext: .constant(true))
// .onTapNext {
// print("Next Tapped")
// }
//
// SimpleInstructionsView(text: "This is just a tutorial asjdkfhla sdfhlakjsd hflkajsd hflkjasdhf lkjasdhf kljasd lajsdfh lkjsdhf lakjsdhf akjsl hfakjsd hadffajsdkfhlkajs hflkjas hfklja hsdjkfl haljskd fhljasdfhlaksd hflas dhfklj asdh fljasdlfjsdhfl.", showPrevious: .constant(true))
// .onTapPrev {
// print("Previous Tapped")
// }
//
// SimpleInstructionsView(text: "This is just a tutorial asjdkfhla sdfhlakjsd hflkajsd hflkjasdhf lkjasdhf kljasd lajsdfh lkjsdhf lakjsdhf akjsl hfakjsd hadffajsdkfhlkajs hflkjas hfklja hsdjkfl haljskd fhljasdfhlaksd hflas dhfklj asdh fljasdlfjsdhfl.", showPrevious: .constant(true), showNext: .constant(true))
// .onTapNext {
// print("Next Tapped")
// }
// .onTapPrev {
// print("Previous Tapped")
// }
// }
// .padding()
// }
// }
//}

View File

@ -1,6 +1,6 @@
//
// SpotlightView.swift
//
//
//
// Created by Muhand Jumah on 4/23/23.
//
@ -11,60 +11,36 @@ internal struct SpotlightView<Chunk: FlowChunk>: View {
private var chunk: Chunk
private var namespace: Namespace.ID
private var namespace2: Namespace.ID
internal init(chunk: Chunk, namespace: Namespace.ID, namespace2: Namespace.ID) {
self.chunk = chunk
self.namespace = namespace
self.namespace2 = namespace2
}
internal var body: some View {
ZStack {
self.chunk.spotlightBackground.body(self.namespace)
// GeometryReader { reader in
self.chunk.spotlightShape.body(self.namespace)
.blendMode(.destinationOut)
.matchedGeometryEffect(id: 10000, in: self.namespace2, isSource: true)
// .overlay(
// instructionsOverlay(reader: reader),
// alignment: .top
// )
// }
.matchedGeometryEffect(
id: self.chunk.id,
in: namespace,
properties: .frame, anchor: .center,
isSource: false
)
// .blendMode(.destinationOut)
self.chunk.spotlightShape.body(self.namespace)
.blendMode(.destinationOut)
.matchedGeometryEffect(
id: 10001,
in: self.namespace2,
properties: .position,
anchor: self.chunk.instructionsViewPosition == .above ? .top : .bottom,
isSource: true
)
.matchedGeometryEffect(
id: self.chunk.id,
in: namespace,
properties: .frame,
anchor: .center,
isSource: false
)
}
.compositingGroup()
}
}
// MARK: - Helper Methods
//private extension SpotlightView {
// func instructionsOverlay(reader: GeometryProxy) -> some View {
// ZStack {
// if(self.chunk.instructions.count > 0) {
// Text("TEST")
// }
//// if (self.chunk.instructionsBubble != nil) {
//// self.chunk.instructionsBubble!.body
//// .padding(.horizontal)
//// .frame(width: UIScreen.main.bounds.size.width)
//// .offset(x: 0, y: self.getYOffset(position: self.chunk.instructionsBubble!.position, in: reader.size))
//// }
// }
// }
//
// func getYOffset(position: InstructionsBubblePosition, in frame: CGSize) -> CGFloat {
// switch position {
// case .above:
// return -(frame.height + 20)
// default:
// return (frame.height + 20)
// }
// }
//}

View File

@ -17,6 +17,7 @@
B69AAB4529F31E6700026789 /* FutureFlow in Frameworks */ = {isa = PBXBuildFile; productRef = B69AAB4429F31E6700026789 /* FutureFlow */; };
B69AAB4729F42F8900026789 /* Demo2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69AAB4629F42F8900026789 /* Demo2.swift */; };
B69AAB4929F44D2A00026789 /* Demo10.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69AAB4829F44D2A00026789 /* Demo10.swift */; };
B6B551EE29FDCB6E006CA32D /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = B6B551ED29FDCB6E006CA32D /* README.md */; };
B6C55E9729F5C75200E9E281 /* Demo11.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C55E9629F5C75200E9E281 /* Demo11.swift */; };
/* End PBXBuildFile section */
@ -50,6 +51,7 @@
B69AAB3429F31CF500026789 /* FutureFlowDemoUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FutureFlowDemoUITestsLaunchTests.swift; sourceTree = "<group>"; };
B69AAB4629F42F8900026789 /* Demo2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Demo2.swift; sourceTree = "<group>"; };
B69AAB4829F44D2A00026789 /* Demo10.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Demo10.swift; sourceTree = "<group>"; };
B6B551ED29FDCB6E006CA32D /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
B6C55E9629F5C75200E9E281 /* Demo11.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Demo11.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -103,13 +105,11 @@
B69AAB1629F31CF300026789 /* FutureFlowDemo */ = {
isa = PBXGroup;
children = (
B6B551EC29FDCB4E006CA32D /* NotRelated */,
B69AAB1729F31CF300026789 /* FutureFlowDemoApp.swift */,
B69AAB1929F31CF300026789 /* ContentView.swift */,
B69AAB4629F42F8900026789 /* Demo2.swift */,
B69AAB1D29F31CF400026789 /* Preview Content */,
B69AAB1B29F31CF400026789 /* Assets.xcassets */,
B69AAB4829F44D2A00026789 /* Demo10.swift */,
B6C55E9629F5C75200E9E281 /* Demo11.swift */,
);
path = FutureFlowDemo;
sourceTree = "<group>";
@ -146,6 +146,17 @@
name = Frameworks;
sourceTree = "<group>";
};
B6B551EC29FDCB4E006CA32D /* NotRelated */ = {
isa = PBXGroup;
children = (
B69AAB4829F44D2A00026789 /* Demo10.swift */,
B6C55E9629F5C75200E9E281 /* Demo11.swift */,
B69AAB4629F42F8900026789 /* Demo2.swift */,
B6B551ED29FDCB6E006CA32D /* README.md */,
);
path = NotRelated;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -253,6 +264,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B6B551EE29FDCB6E006CA32D /* README.md in Resources */,
B69AAB1F29F31CF400026789 /* Preview Assets.xcassets in Resources */,
B69AAB1C29F31CF400026789 /* Assets.xcassets in Resources */,
);

View File

@ -8,12 +8,73 @@
import SwiftUI
import FutureFlow
struct ParentWrapper: InstructionsView {
@Binding var tutorial: TutorialChunks
let next: () -> ()
let back: () -> ()
@State private var instructions: [String] = []
@Namespace var ns
var body: some View {
RectanglesTutorial(instructions: self.$instructions)
.onDone({ withNext in
if(withNext) {
self.next()
} else {
self.back()
}
})
.animation(.easeOut, value: self.tutorial)
.onAppear {
switch self.tutorial {
case .red1:
self.instructions = ["This is red"]
break
case .red2:
self.instructions = ["Red is purple now??? WHAT?"]
break
case .red3:
self.instructions = ["No way, IT IS BLUE!!"]
break
case .yellow:
self.instructions = ["This is yellow"]
break
case .green:
self.instructions = ["This is green"]
break
}
}
.onChange(of: self.tutorial) { newValue in
switch newValue {
case .red1:
self.instructions = ["This is red"]
break
case .red2:
self.instructions = ["Red is purple now??? WHAT?"]
break
case .red3:
self.instructions = ["No way, IT IS BLUE!!"]
break
case .yellow:
self.instructions = ["This is yellow"]
break
case .green:
self.instructions = ["This is green"]
break
}
}
}
}
enum TutorialChunks: FlowChunk, CaseIterable {
case red
case red1
case red2
case red3
case green
case yellow
var spotlightShape: SpotlightShape {
switch self {
case .yellow:
@ -22,7 +83,7 @@ enum TutorialChunks: FlowChunk, CaseIterable {
return .rectangle()
}
}
var spotlightBackground: SpotlightBackground {
switch self {
case .green:
@ -31,62 +92,117 @@ enum TutorialChunks: FlowChunk, CaseIterable {
return .black
}
}
var instructionsViewType: InstructionsViewType? {
var instructionsViewPosition: InstructionsViewPosition {
switch self {
case .red:
return .simple(instructions: ["Text1"], position: .below)
case .red1, .red2, .red3:
return .below
case .green:
return .simple(instructions: ["Text2", "Text22", "Text222", "Text2222"], position: .below)
return .above
case .yellow:
return .simple(instructions: ["Text3"], position: .above)
return .above
}
}
func instructionsView(_ next: @escaping () -> (), _ back: @escaping () -> ()) -> FutureFlow.AnyInstructionsView? {
switch self {
case .red1, .red2:
ParentWrapper(tutorial:
.init(
get: {
self
}, set: { _ in }
),
next: next,
back: back
)
default:
ParentWrapper(tutorial:
.init(
get: {
self
}, set: { _ in }
),
next: next,
back: back
)
}
}
func hash(into hasher: inout Hasher) {
switch self {
case .red1, .red2, .red3:
hasher.combine(0)
case .green:
hasher.combine(1)
case .yellow:
hasher.combine(2)
}
}
}
struct ContentView: View {
private var uniqueIdentifier: String = UUID().uuidString
private var tutorial: [TutorialChunks] =
[
.red,
.green,
.yellow
]
@State private var showTutorial: Bool = true
struct ContentView: View {
@State private var rectColor: Color = .red
private var uniqueIdentifier: String = UUID().uuidString
@State private var showTutorial: Bool = true
@Namespace var namespace
var body: some View {
VStack(spacing: 50) {
Rectangle()
.frame(width: 256, height: 256, alignment: .center)
.foregroundColor(Color.red)
.foregroundColor(self.rectColor)
.configureChunkForSpotlight(
namespace: self.namespace,
parentIdentifier: self.uniqueIdentifier,
chunk: self.tutorial[0]
chunk: TutorialChunks.red1
)
Rectangle()
.frame(width: 64, height: 128, alignment: .center)
.foregroundColor(Color.green)
.configureChunkForSpotlight(
namespace: self.namespace,
parentIdentifier: self.uniqueIdentifier,
chunk: self.tutorial[1]
chunk: TutorialChunks.green
)
Circle()
.frame(width: 128, height: 128, alignment: .center)
.foregroundColor(.yellow)
.configureChunkForSpotlight(
namespace: self.namespace,
parentIdentifier: self.uniqueIdentifier,
chunk: self.tutorial[2]
chunk: TutorialChunks.yellow
)
.onTapGesture {
// withAnimation(.linear) {
self.showTutorial = true
// }
// withAnimation(.linear) {
self.showTutorial = true
// }
}
}
.padding()
.assembleSpotlightChunks(uniqueIdentifier: self.uniqueIdentifier, chunks: Array(TutorialChunks.allCases), showTutorial: self.$showTutorial)
.assembleSpotlightChunks(namespace:self.namespace, uniqueIdentifier: self.uniqueIdentifier, chunks: Array(TutorialChunks.allCases), showTutorial: self.$showTutorial) {
chunk in
withAnimation(.easeOut) {
switch chunk {
case .red2:
self.rectColor = .purple
break
case .red3:
self.rectColor = .blue
break
default:
self.rectColor = .red
break
}
}
}
}
}
@ -95,3 +211,108 @@ struct ContentView_Previews: PreviewProvider {
ContentView()
}
}
internal struct RectanglesTutorial: InstructionsView {
@Binding private var instructions: [String]
@Namespace var positionNamespace
private var positionId: String = "steps-position-id"
@State private var showNext: Bool = true
@State private var showPrevious: Bool = true
@State private var currentStep: Int = 0
private var onDone: ((_ withNext: Bool) -> ())?
internal init(instructions: Binding<[String]>) {
self._instructions = instructions
// self.onDone = { _ in }
self.onDone = nil
}
fileprivate init(instructions: Binding<[String]>, onDone: @escaping (_ withNext: Bool) -> ()) {
self._instructions = instructions
self.onDone = onDone
}
var body: some View {
VStack(alignment: .leading, spacing: 0) {
HStack(alignment: .top, spacing: 10) {
Image(systemName: "lightbulb.circle.fill")
.font(.system(size: 32))
.foregroundColor(.blue)
if(self.currentStep < self.instructions.count) {
Text(self.instructions[self.currentStep])
.font(.body)
.animation(.easeOut, value: self.currentStep)
}
}
.matchedGeometryEffect(id: positionId, in: self.positionNamespace, properties: .position, anchor: .bottomTrailing, isSource: true)
HStack(alignment: .center, spacing:15) {
if(self.showPrevious) {
Button(action: {
guard self.currentStep > 0 else {
if let onDone = self.onDone {
onDone(false)
}
return
}
self.currentStep -= 1
}) {
Text("Previous")
.font(.callout)
.fontWeight(.medium)
.foregroundColor(.blue)
}
}
if(self.showNext) {
Button(action: {
guard self.currentStep < self.instructions.count - 1 else {
if let onDone = self.onDone {
onDone(true)
}
return
}
self.currentStep += 1
}) {
Text("Next")
.font(.callout)
.fontWeight(.semibold)
.padding(.horizontal, 10)
.padding(.vertical, 5)
.background(Color.blue)
.cornerRadius(3)
.foregroundColor(.white)
}
}
}
.transition(.slide)
.animation(.linear, value: self.showNext)
.animation(.linear, value: self.showPrevious)
.padding(.top, 10)
.matchedGeometryEffect(id: positionId, in: self.positionNamespace, properties: .position, anchor: .topTrailing, isSource: false)
}
.padding(.horizontal, 20)
.padding(.vertical, 15)
.background(Color.white)
.cornerRadius(10)
.shadow(color: .black.opacity(0.2), radius: 5, x: 0, y: 0)
.animation(.easeOut, value: self.currentStep)
.onAppear {
self.currentStep = 0
}
.onChange(of: self.instructions) { newV in
self.currentStep = 0
}
}
}
internal extension RectanglesTutorial {
func onDone(_ callback: @escaping (_ withNext: Bool) -> ()) -> RectanglesTutorial {
.init(instructions: self.$instructions, onDone: callback)
}
}

View File

@ -1,19 +0,0 @@
////
//// Demo11.swift
//// FutureFlowDemo
////
//// Created by Muhand Jumah on 4/23/23.
////
//
//import SwiftUI
//
//struct Demo11: View {
//
//}
//
//struct Demo11_Previews: PreviewProvider {
// static var previews: some View {
//
//
// }
//}

View File

@ -0,0 +1,216 @@
//////
////// Demo11.swift
////// FutureFlowDemo
//////
////// Created by Muhand Jumah on 4/23/23.
//////
////
////import SwiftUI
////
////struct Demo11: View {
//// var body: some View {
//// ScrollView {
//// Text (
//// "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec et orci metus. Donec consectetur laoreet finibus. Pellentesque at maximus leo. Donec nec elementum metus, ut interdum libero. Cras hendrerit neque non magna sodales, eu lacinia justo laoreet. Phasellus pharetra vehicula nibh, porttitor elementum velit vestibulum id. Pellentesque quis mattis risus, eget feugiat justo. Maecenas a tortor neque. Nullam placerat non mauris eget tristique. Nam auctor dolor sit amet tincidunt pulvinar.\n\nNunc cursus eget quam ut convallis. In hac habitasse platea dictumst. Integer id elit ac tellus vehicula viverra eu ac metus. Cras tempus nisl eget nulla porttitor, placerat sollicitudin justo tincidunt. Phasellus et leo mi. Curabitur convallis malesuada mi tincidunt scelerisque. Sed rutrum ultricies ligula. Pellentesque nisl metus, euismod venenatis dui sit amet, lobortis lacinia quam. Nullam ipsum est, tempus et nisl nec, consectetur fermentum nulla. Fusce velit augue, consectetur nec purus id, maximus bibendum elit. Phasellus ornare odio sit amet erat tristique dapibus. Aliquam erat volutpat. Nam hendrerit diam eget arcu dapibus lacinia. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ligula augue, aliquam nec condimentum quis, consectetur a erat.\nFusce at ipsum leo. Morbi egestas sem sit amet velit vestibulum gravida. Nullam eu finibus lorem. Nam ut lacus turpis. Nullam eu sagittis dui. Ut et condimentum purus. Sed et lectus pretium, imperdiet nulla sit amet, pulvinar enim. Suspendisse blandit tempus varius. Sed at consequat orci. Quisque feugiat malesuada mauris nec bibendum. Aenean condimentum ornare leo, quis porttitor risus malesuada vitae. Duis interdum odio ut scelerisque aliquet. Nam et viverra felis. Nulla eu nisi eu nisi condimentum bibendum at in odio. Aenean ut arcu quam.\nInteger non tristique turpis. Vestibulum non dolor metus. Phasellus odio lorem, consequat quis fermentum nec, faucibus scelerisque orci. Sed id placerat nibh, quis ultrices diam. Sed laoreet quis massa vel tincidunt. Proin maximus accumsan convallis. Morbi sed mi ac turpis condimentum faucibus. Donec eros nunc, dignissim vitae nibh a, pulvinar suscipit mi. Nam libero odio, rhoncus id eleifend sed, porttitor ut metus. Nunc fringilla eget ligula vel dictum. Aenean eget posuere eros. Sed placerat nec sem at faucibus. Sed vehicula sollicitudin lectus, ut consequat quam commodo in.\nIn id facilisis ligula, ut aliquet urna. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nullam dictum blandit metus vitae dignissim. Nunc ac orci sed lorem volutpat tincidunt. Nulla vel purus nunc. Duis rutrum lorem et semper scelerisque. Quisque rutrum lorem eu arcu ultricies semper. Mauris auctor vulputate est non aliquam. Vivamus viverra ullamcorper sem, et consectetur est dapibus ut. Aliquam at massa sed lectus hendrerit aliquam in et mi. Sed venenatis tincidunt sem, ut rutrum augue vestibulum vitae. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Phasellus hendrerit quam et erat finibus tempor. Donec vehicula ut mi non ullamcorper. Sed lorem elit, hendrerit eu volutpat ut, tincidunt vel leo. "
//// )
//// .fixedSize(horizontal: false, vertical: true)
//// .frame(maxWidth: .infinity, maxHeight: .infinity)
//// }.frame(maxHeight: .infinity).padding()
//// }
////}
////
////struct Demo11_Previews: PreviewProvider {
//// static var previews: some View {
//// Demo11()
//// }
////}
//
//import SwiftUI
//@available(iOS 16.0, *)
//extension Image {
// @MainActor func frame(width: CGFloat, height: CGFloat) -> Image {
// let imageRenderer: ImageRenderer = .init(
// content:
// self
// .resizable()
// .frame(width: width, height: height, alignment: .center)
// )
//
// if let uiImage = imageRenderer.uiImage {
// return Image(uiImage: uiImage)
// } else {
// return
// Image(systemName: "x.circle")
// }
// }
//}
//
//@available(iOS 16.0, *)
//struct Demo11: View {
// var body: some View {
//// ScrollView(.vertical) {
//// VStack(alignment: .leading, spacing: 10) {
//// HStack(alignment: .top, spacing: 10) {
//// Image(systemName: "square.fill")
//// .resizable()
//// .frame(width: 80, height: 80)
//// .foregroundColor(.black)
////
//// Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed fermentum vulputate euismod. In facilisis diam et mollis consequat jkahdfk ladshfkljahsd fjlkahsd fjlkhasd kjlhasdjk faljk dsfljah sdfjlk hasdklj asdnf msdnf m,.asnf asdjhf ljaskhf lkjasdh fljakshd fljkash. ")
//// }
////
//// Text("Ut vulputate ultrices erat, sit amet auctor felis tristique vel. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi felis leo, blandit cursus tortor eu, ullamcorper volutpat orci. Donec ornare velit sed neque scelerisque, sit amet malesuada nunc laoreet. Ut non lobortis felis, vel ultrices ligula. Nam massa mauris, volutpat et felis lacinia, dignissim efficitur arcu. Vestibulum euismod in risus at consequat. Nunc a vestibulum dolor. Vivamus sed augue lectus. Duis felis leo, gravida quis est ac, volutpat suscipit nulla. Pellentesque pharetra vulputate erat ut lobortis. Vivamus sed nibh enim. Phasellus pharetra malesuada justo quis molestie. Sed eget lectus at turpis elementum posuere quis ut velit. Morbi suscipit venenatis tempus. Ut sed arcu ipsum. ")
//// }
//// }
//// .padding()
//
// ScrollView(.vertical) {
//// Text("Ut vulputate ultrices erat, sit amet auctor felis tristique vel. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi felis leo, blandit cursus tortor eu, ullamcorper volutpat orci. Donec ornare velit sed neque scelerisque, sit amet malesuada nunc laoreet. Ut non lobortis felis, vel ultrices ligula. Nam massa mauris, volutpat et felis lacinia") + Text(Image(systemName: "square.fill")) + Text(", dignissim efficitur arcu. Vestibulum euismod in risus at consequat. Nunc a vestibulum dolor. Vivamus sed augue lectus. Duis felis leo, gravida quis est ac, volutpat suscipit nulla. Pellentesque pharetra vulputate erat ut lobortis. Vivamus sed nibh enim. Phasellus pharetra malesuada justo quis molestie. Sed eget lectus at turpis elementum posuere quis ut velit. Morbi suscipit venenatis tempus. Ut sed arcu ipsum.")
// AsyncImage(url: .init(string: "https://1000logos.net/wp-content/uploads/2016/10/Apple-Logo.png")) { img in
//// Text("Ut vulputate ultrices erat, sit amet auctor felis tristique vel. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi felis leo, blandit cursus tortor eu, ullamcorper volutpat orci. Donec ornare velit sed neque scelerisque, sit amet malesuada nunc laoreet. Ut non lobortis felis, vel ultrices ligula. Nam massa mauris, volutpat et felis lacinia") +
////// Text(img.frame(width: 80, height: 80)) +
//// Text(Image(systemName: "x.circle")) +
//// Text(", dignissim efficitur arcu. Vestibulum euismod in risus at consequat. Nunc a vestibulum dolor. Vivamus sed augue lectus. Duis felis leo, gravida quis est ac, volutpat suscipit nulla. Pellentesque pharetra vulputate erat ut lobortis. Vivamus sed nibh enim. Phasellus pharetra malesuada justo quis molestie. Sed eget lectus at turpis elementum posuere quis ut velit. Morbi suscipit venenatis tempus. Ut sed arcu ipsum.")
//
// Text(
// "Ut vulputate ultrices erat, sit amet auctor felis tristique vel. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi felis leo, blandit cursus tortor eu, ullamcorper volutpat orci. Donec ornare velit sed neque scelerisque, sit amet malesuada nunc laoreet. Ut non lobortis felis, vel ultrices ligula. Nam massa mauris, volutpat et felis lacinia"
// + Image(systemName: "x.circle")
// + ", dignissim efficitur arcu. Vestibulum euismod in risus at consequat. Nunc a vestibulum dolor. Vivamus sed augue lectus. Duis felis leo, gravida quis est ac, volutpat suscipit nulla. Pellentesque pharetra vulputate erat ut lobortis. Vivamus sed nibh enim. Phasellus pharetra malesuada justo quis molestie. Sed eget lectus at turpis elementum posuere quis ut velit. Morbi suscipit venenatis tempus. Ut sed arcu ipsum."
// )
//// let m: Image = img
////// .resizable()
////// .scaledToFill()
////// .frame(width: 80, height: 80)
////
//// Text("TEST") +
//// Text(m.frame(width: 80, height: 80)).font(.system(size: 100)) +
//// Text("test2")
// } placeholder: {
// Text("Loading...")
// }
//
//// HStack(alignment: .top, spacing: 10) {
//// VStack(alignment: .leading, spacing: 10) {
//// Image(systemName: "square.fill")
//// .resizable()
//// .frame(width: 80, height: 80)
//// .foregroundColor(.black)
////
//// Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed fermentum vulputate euismod. In facilisis diam et mollis consequat jkahdfk ladshfkljahsd fjlkahsd fjlkhasd kjlhasdjk faljk dsfljah sdfjlk hasdklj asdnf msdnf m,.asnf asdjhf ljaskhf lkjasdh fljakshd fljkash. ")
//// }
//// .frame(maxWidth: 80, alignment: .topLeading)
////
//// Text("Ut vulputate ultrices erat, sit amet auctor felis tristique vel. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi felis leo, blandit cursus tortor eu, ullamcorper volutpat orci. Donec ornare velit sed neque scelerisque, sit amet malesuada nunc laoreet. Ut non lobortis felis, vel ultrices ligula. Nam massa mauris, volutpat et felis lacinia, dignissim efficitur arcu. Vestibulum euismod in risus at consequat. Nunc a vestibulum dolor. Vivamus sed augue lectus. Duis felis leo, gravida quis est ac, volutpat suscipit nulla. Pellentesque pharetra vulputate erat ut lobortis. Vivamus sed nibh enim. Phasellus pharetra malesuada justo quis molestie. Sed eget lectus at turpis elementum posuere quis ut velit. Morbi suscipit venenatis tempus. Ut sed arcu ipsum. ")
//// }
// }
// .padding()
//
//
//// Text("Ut vulputate ultrices erat, sit amet auctor felis tristique vel. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi felis leo, blandit cursus tortor eu, ullamcorper volutpat orci. Donec ornare velit sed neque scelerisque, sit amet malesuada nunc laoreet. Ut non lobortis felis, vel ultrices ligula. Nam massa mauris, volutpat et felis lacinia") +
////// Text(img.frame(width: 80, height: 80)) +
//// Text(Image(systemName: "x.circle")).font(.system(size: 20)) +
//// Text(", dignissim efficitur arcu. Vestibulum euismod in risus at consequat. Nunc a vestibulum dolor. Vivamus sed augue lectus. Duis felis leo, gravida quis est ac, volutpat suscipit nulla. Pellentesque pharetra vulputate erat ut lobortis. Vivamus sed nibh enim. Phasellus pharetra malesuada justo quis molestie. Sed eget lectus at turpis elementum posuere quis ut velit. Morbi suscipit venenatis tempus. Ut sed arcu ipsum.")
// }
//}
//
////struct DynamicTextModifier: ViewModifier {
//// let imageWidth: CGFloat
//// let text: String
//// let font: Font
////
//// func body(content: Content) -> some View {
//// GeometryReader { geometry in
//// let lineWidth = geometry.size.width - imageWidth
//// let textFragments = splitTextToFitWidth(text: text, lineWidth: lineWidth, font: font)
////
//// VStack(alignment: .leading, spacing: 0) {
//// ForEach(textFragments.indices, id: \.self) { index in
//// if index == 0 {
//// HStack {
//// Spacer(minLength: imageWidth)
//// Text(textFragments[index]).font(font)
//// }
//// } else {
//// Text(textFragments[index]).font(font)
//// }
//// }
//// }
//// }
//// }
////
//// private func splitTextToFitWidth(text: String, lineWidth: CGFloat, font: Font) -> [String] {
//// let words = text.split(separator: " ")
//// var textFragments: [String] = []
//// var currentLine = ""
////
//// for word in words {
//// let newLine = currentLine.isEmpty ? "\(word)" : "\(currentLine) \(word)"
//// let newSize = newLine.size(usingFont: font)
////
//// if newSize.width <= lineWidth {
//// currentLine = newLine
//// } else {
//// textFragments.append(currentLine)
//// currentLine = "\(word)"
//// }
//// }
////
//// if !currentLine.isEmpty {
//// textFragments.append(currentLine)
//// }
////
//// return textFragments
//// }
////}
////
////extension String {
//// func size(usingFont font: Font) -> CGSize {
//// let nsString = self as NSString
//// let fontAttributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: font.size)] // Adjust this line if you use a custom font
//// return nsString.size(withAttributes: fontAttributes)
//// }
////}
////
////extension Font {
//// var size: CGFloat {
//// switch self {
//// case .largeTitle: return UIFont.preferredFont(forTextStyle: .largeTitle).pointSize
//// case .title: return UIFont.preferredFont(forTextStyle: .title1).pointSize
//// case .title2: return UIFont.preferredFont(forTextStyle: .title2).pointSize
//// case .title3: return UIFont.preferredFont(forTextStyle: .title3).pointSize
//// case .headline: return UIFont.preferredFont(forTextStyle: .headline).pointSize
//// case .subheadline: return UIFont.preferredFont(forTextStyle: .subheadline).pointSize
//// case .body: return UIFont.preferredFont(forTextStyle: .body).pointSize
//// case .callout: return UIFont.preferredFont(forTextStyle: .callout).pointSize
//// case .caption: return UIFont.preferredFont(forTextStyle: .caption1).pointSize
//// case .caption2: return UIFont.preferredFont(forTextStyle: .caption2).pointSize
//// case .footnote: return UIFont.preferredFont(forTextStyle: .footnote).pointSize
//// default: return UIFont.preferredFont(forTextStyle: .body).pointSize
//// }
//// }
////}
////
////import SwiftUI
////
////struct Demo11: View {
//// @State private var dynamicText: String = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut dapibus malesuada turpis, non posuere est laoreet nec. Sed maximus bibendum odio, a rhoncus nisl facilisis a. Curabitur vitae consequat justo."
////
//// var body: some View {
//// ZStack(alignment: .topLeading) {
//// Color.clear.modifier(DynamicTextModifier(imageWidth: 80, text: dynamicText, font: .body))
////
//// Image(systemName: "square.fill") // Replace with your own image
//// .resizable()
//// .frame(width: 100, height: 100) // Adjust the frame size to match your image size
//// .background(Color.red) // Optional, just to visualize the image frame
//// }
//// .padding()
//// }
////}
////
//@available(iOS 16.0, *)
//struct Demo11_Previews: PreviewProvider {
// static var previews: some View {
// Demo11()
// }
//}

View File

@ -0,0 +1 @@
**NOTE:** Ignore this directory. Nothing here is important, this directory will be deleted soon.