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
10 changes: 9 additions & 1 deletion .github/workflows/swift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@ jobs:
runs-on: macos-latest

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: List available Xcode versions
run: ls /Applications | grep Xcode
- name: Show current version of Xcode
run: xcodebuild -version
- name: Set up Xcode version
run: sudo xcode-select -s /Applications/Xcode_16.1.0.app/Contents/Developer
- name: Show current version of Xcode
run: xcodebuild -version
- name: Install Bundle
run: bundle install
- name: Run unit tests
Expand Down
188 changes: 157 additions & 31 deletions BackgroundTransferRevised-Example.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1600"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "43D1CF4E2D80860C00AC1ED9"
BuildableName = "BackgroundTransferRevised-Example.app"
BlueprintName = "BackgroundTransferRevised-Example"
ReferencedContainer = "container:BackgroundTransferRevised-Example.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "43D1CF5E2D80860E00AC1ED9"
BuildableName = "BackgroundTransferRevised-ExampleTests.xctest"
BlueprintName = "BackgroundTransferRevised-ExampleTests"
ReferencedContainer = "container:BackgroundTransferRevised-Example.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "43D1CF4E2D80860C00AC1ED9"
BuildableName = "BackgroundTransferRevised-Example.app"
BlueprintName = "BackgroundTransferRevised-Example"
ReferencedContainer = "container:BackgroundTransferRevised-Example.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "43D1CF4E2D80860C00AC1ED9"
BuildableName = "BackgroundTransferRevised-Example.app"
BlueprintName = "BackgroundTransferRevised-Example"
ReferencedContainer = "container:BackgroundTransferRevised-Example.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
32 changes: 32 additions & 0 deletions BackgroundTransferRevised-Example/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// AppDelegate.swift
// BackgroundTransferRevised-Example
//
// Created by William Boles on 26/03/2025.
//

import UIKit
import OSLog

class AppDelegate: NSObject, UIApplicationDelegate {
static var shared: AppDelegate?

private var backgroundCompletionHandler: (() -> Void)?
private let logger = Logger(subsystem: "com.williamboles",
category: "appDelegate")

// MARK: - Background

func application(_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void) {
self.backgroundCompletionHandler = completionHandler
}

func backgroundDownloadsComplete() {
logger.info("Triggering background session completion handler")

backgroundCompletionHandler?()
backgroundCompletionHandler = nil
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// BackgroundTransferRevised_ExampleApp.swift
// BackgroundTransferRevised-Example
//
// Created by William Boles on 11/03/2025.
//

import SwiftUI
import OSLog

@main
struct BackgroundTransferRevised_ExampleApp: App {
@Environment(\.scenePhase) private var scenePhase
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

private let logger = Logger(subsystem: "com.williamboles",
category: "app")


// MARK: - Init

init() {
AppDelegate.shared = appDelegate
}

// MARK: - Scene

var body: some Scene {
WindowGroup {
let catsViewModel = CatsViewModel()
CatsView(viewModel: catsViewModel)
}
.onChange(of: scenePhase) { (_, newPhase) in
guard newPhase == .background else {
return
}

logger.info("Files will be downloaded to: \(FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].absoluteString)")

// //Exit app to test restoring app from a terminated state. Comment out to test restoring app from a suspended state.
// Task {
// logger.info("Simulating app termination by exit(0)")
//
// exit(0)
// }
}
}
}
11 changes: 11 additions & 0 deletions BackgroundTransferRevised-Example/Application/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
</array>
</dict>
</plist>

This file was deleted.

This file was deleted.

This file was deleted.

24 changes: 0 additions & 24 deletions BackgroundTransferRevised-Example/ContentView.swift

This file was deleted.

22 changes: 22 additions & 0 deletions BackgroundTransferRevised-Example/Extensions/GridItem+Layout.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// GridItem+Layout.swift
// BackgroundTransfer-Example
//
// Created by William Boles on 11/03/2025.
//

import Foundation
import SwiftUI

extension GridItem {

static func threeFlexibleColumns() -> [GridItem] {
let columns = [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible()),
]

return columns
}
}
70 changes: 70 additions & 0 deletions BackgroundTransferRevised-Example/Features/Cats/CatsView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//
// ContentView.swift
// BackgroundTransferRevised-Example
//
// Created by William Boles on 11/03/2025.
//

import SwiftUI

struct CatsView: View {
@StateObject var viewModel: CatsViewModel

// MARK: - View

var body: some View {
NavigationStack {
VStack {
switch viewModel.state {
case .empty:
Text("We have no cats to show you! 🙀")
case .retrieving:
ProgressView("Retrieving Cats! 😺")
case .retrieved(let cats):
GeometryReader { geometryReader in
let columns = GridItem.threeFlexibleColumns()
let sideLength = geometryReader.size.width / CGFloat(columns.count)
ScrollView {
LazyVGrid(columns: columns, alignment: .center, spacing: 4) {
ForEach(cats) { catViewModel in
CatImageCell(viewModel: catViewModel)
.frame(width: sideLength, height: sideLength)
.task {
await catViewModel.loadImage()
}
}
}
}
}
case .failed:
Text("Failed to retrieve Cats! 😿")
}
}
.padding()
.navigationTitle("Cats 😻")
}
.task {
await viewModel.retrieveCats()
}
}
}

struct CatImageCell: View {
@StateObject var viewModel: CatViewModel

// MARK: - View

var body: some View {
switch viewModel.state {
case .empty:
Image(systemName: "photo")
case .retrieving:
ProgressView()
case .retrieved(let image):
image.resizable()
.aspectRatio(contentMode: .fill)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.clipped()
}
}
}
Loading