Skip to content

Commit

Permalink
Merge pull request #5 from macblazer/improve-documentation
Browse files Browse the repository at this point in the history
Improve documentation
  • Loading branch information
macblazer authored Oct 26, 2022
2 parents 309318f + fc1a600 commit 3fcabd1
Show file tree
Hide file tree
Showing 17 changed files with 352 additions and 60 deletions.
54 changes: 54 additions & 0 deletions .github/workflows/publish_docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Build and Publish Docs

on:
workflow_dispatch:
push:
branches:
- 'main'

# Kill any previous run still executing
concurrency:
group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
cancel-in-progress: true

jobs:
build_docs:
name: Build and Archive Docs
runs-on: macos-12
steps:
- name: Checkout
uses: actions/checkout@v3

- name: Generate docs
run: |
swift package \
--allow-writing-to-directory github-pages \
generate-documentation \
--target ManagedAppConfigLib \
--disable-indexing \
--transform-for-static-hosting \
--hosting-base-path ManagedAppConfigLib/ \
--output-path github-pages
- name: Upload docs archive
uses: actions/upload-pages-artifact@main
with:
path: github-pages

deploy:
name: Deploy Docs
needs: build_docs

permissions:
pages: write
id-token: write

environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}

runs-on: ubuntu-latest
steps:
- name: Deploy
id: deployment
uses: actions/deploy-pages@v1
25 changes: 25 additions & 0 deletions .github/workflows/run_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Run unit tests

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
paths:
- '**.swift'

# Kill any previous run still executing
concurrency:
group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
cancel-in-progress: true

jobs:
spm_tests:
name: Run package tests
runs-on: macos-12
steps:
- name: Checkout
uses: actions/checkout@v3

- name: Unit Test
run: swift test -v
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
xcuserdata
.DS_Store
.swiftpm
.build
Package.resolved
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased
## [1.1.0] - 2022-10-28
### Added
- Property wrapper for SwiftUI and non-SwiftUI to make grabbing AppConfig values very simple.
- Additional documentation for use with DocC.
- Add unit tests and run them through GitHub Actions when swift files change.
- Publish docs from main branch in GitHub Actions.

### Changed
- Updated documentation for `ManagedAppConfig` class.
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright 2017 JAMF Software LLC
Copyright 2022 Jamf Open Source Community

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:

Expand Down
8 changes: 4 additions & 4 deletions ManagedAppConfigLib.podspec
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
Pod::Spec.new do |s|
s.name = "ManagedAppConfigLib"
s.version = "1.0.0"
s.summary = "A facade that simplifies working with Managed App Configuration and Feedback."
s.version = "1.1.0"
s.summary = "Simplify working with Managed App Configuration and Feedback."

s.description = <<-DESC
The purpose of ManagedAppConfigLib is to make it that much easier to work with Apple's [Managed App Configuration](https://developer.apple.com/library/content/samplecode/sc2279/Introduction/Intro.html) by providing a few convenience methods.
The purpose of ManagedAppConfigLib is to make it easier to work with Apple's [Managed App Configuration](https://developer.apple.com/library/content/samplecode/sc2279/Introduction/Intro.html) by providing a couple property wrappers and an object-based approach.
DESC

s.homepage = "https://appconfig.org/"
s.license = { :type => "MIT", :file => "LICENSE" }
s.author = { "James Felton" => "[email protected]" }
s.author = "Kyle Hammond", "James Felton"

s.swift_versions = "5.1"
s.platform = :ios, "8.0"
Expand Down
5 changes: 4 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.1
// swift-tools-version:5.6
import PackageDescription

let package = Package(
Expand All @@ -11,6 +11,9 @@ let package = Package(
products: [
.library(name: "ManagedAppConfigLib", targets: ["ManagedAppConfigLib"])
],
dependencies: [
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0")
],
targets: [
.target(name: "ManagedAppConfigLib"),
.testTarget(name: "ManagedAppConfigLibTests", dependencies: ["ManagedAppConfigLib"])
Expand Down
27 changes: 16 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,16 @@ dependencies: [
],
```

## Usage
You will need to `import ManagedAppConfigLib` in each Swift file you wish to use it. You can choose
to use the read-only property wrappers, or make use of the `ManagedAppConfig` class for read and write access.
## Complete Documentation

[The documentation](https://jamf.github.io/ManagedAppConfigLib/documentation/managedappconfiglib/)
for this package was generated by [DocC](https://developer.apple.com/documentation/docc)
from GitHub Actions. Take a look at the workflow file
[publish_docs.yml](https://github.com/jamf/ManagedAppConfigLib/blob/main/.github/workflows/publish_docs.yml)
for more details.

## Basic Usage
You will need to `import ManagedAppConfigLib` in each Swift file you wish to use it.

### SwiftUI Property Wrapper

Expand Down Expand Up @@ -60,26 +67,24 @@ This is useful for UIKit or AppKit code or simple Foundation code in models or c

### Simple functional use

* Retrieve a value set by MDM from the Managed App Configuration:
* Retrieve a Managed App Configuration value
```swift
if let deviceId = ManagedAppConfig.shared.getConfigValue(forKey: "deviceId") as? String {
print(deviceId)
}
```

* Register a closure to be executed when the managed app configuration dictionary is changed:
* Register a closure to be executed when Managed App Configuration changes
```swift
let myClosure = { (configDict: [String: Any?]) -> Void in
print("managed app configuration changed")
print("Managed App Configuration changed")
}
ManagedAppConfig.shared.addAppConfigChangedHook(myClosure)

```

* Place a value into the managed app feedback dictionary:
* Place a value into Managed App Feedback
```swift
let exampleKey = "errorCount"
let exampleValue = 0
ManagedAppConfig.shared.updateValue(exampleValue, forKey: exampleKey)

let numberOfErrors = 0
ManagedAppConfig.shared.updateValue(numberOfErrors, forKey: "errorCount")
```
19 changes: 12 additions & 7 deletions Sources/ManagedAppConfigLib/AppConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@
import Foundation
import SwiftUI

/// A property wrapper type that reflects a value from Managed App Config (via `UserDefaults`) and
/// invalidates a SwiftUI view on a change in value in that Managed App Config.
/// A read-only property wrapper type that reflects a value from Managed App Configuration (via `UserDefaults`) and
/// invalidates a SwiftUI view on a change in value in that Managed App Configuration.
///
/// Create an app config value in a SwiftUI `View`, `App`, or `Scene` by applying the `@AppConfig` attribute to a property
/// declaration.
///
/// When the Managed App Config value changes, SwiftUI updates the parts of any view that depend on those properties.
@available(macOS 11, iOS 13.0, tvOS 13.0, *)
@propertyWrapper public struct AppConfig<Value>: DynamicProperty {
// Very simple listener that observes AppConfig changes, and has a local copy of the AppConfig's value.
Expand Down Expand Up @@ -45,7 +50,7 @@ import SwiftUI
private let defaults: UserDefaults
private let defaultValue: Value

/// The value from AppConfig or the defaultValue provided to the initializer.
/// The value from Managed App Configuration or the `defaultValue` provided to the initializer.
public var wrappedValue: Value {
core.value ?? defaultValue
}
Expand All @@ -59,12 +64,12 @@ import SwiftUI

// MARK: Initializers

/// Initializer for standard types
/// Initializer for standard types.
///
/// The `store` parameter is useful for unit tests or reading values from other suites.
/// - Parameters:
/// - defaultValue: The default value for the property if the AppConfig value is not set
/// - key: A key into the AppConfig dictionary
/// - defaultValue: The default value for the property if the Managed App Configuration value is not set.
/// - key: A key into the Managed App Configuration dictionary.
/// - store: A `UserDefaults` object; defaults to the `.standard` object if not given.
public init(wrappedValue defaultValue: Value, _ key: String, store: UserDefaults = UserDefaults.standard) {
self.key = key
Expand All @@ -76,7 +81,7 @@ import SwiftUI
///
/// The `store` parameter is useful for unit tests or reading values from other suites.
/// - Parameters:
/// - key: A key into the AppConfig dictionary
/// - key: A key into the Managed App Configuration dictionary.
/// - store: A `UserDefaults` object; defaults to the `.standard` object if not given.
public init(_ key: String, store: UserDefaults = UserDefaults.standard) where Value: ExpressibleByNilLiteral {
self.key = key
Expand Down
20 changes: 13 additions & 7 deletions Sources/ManagedAppConfigLib/AppConfigPlain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@

import Foundation

/// A property wrapper type that can be used outside of SwiftUI that reflects a value from Managed App Config
/// (via `UserDefaults`) and keeps itself up to date with changes in value in that Managed App Config.
/// A read-only property wrapper type that reflects a value from Managed App Configuration
/// (via `UserDefaults`) and keeps itself up to date with changes in value in that Managed App Configuration.
///
/// Can be used outside of SwiftUI.
///
/// If used in a SwiftUI view, does **not** trigger a SwiftUI view redraw when the
/// Managed App Configuration value changes. See ``AppConfig`` if you want a SwiftUI view to redraw on update
/// of a Managed App Configuration value.
@available(macOS 11, iOS 7.0, tvOS 10.2, *)
@propertyWrapper public struct AppConfigPlain<Value> {
// Very simple listener that observes AppConfig changes, and updates it's internal copy of the value as needed.
Expand Down Expand Up @@ -35,19 +41,19 @@ import Foundation
private var listener = Listener<Value>()
private let defaultValue: Value

/// The value from AppConfig or the defaultValue provided to the initializer.
/// The value from Managed App Configuration or the defaultValue provided to the initializer.
public var wrappedValue: Value {
listener.value ?? defaultValue
}

// MARK: - Initializers

/// Initializer for standard types
/// Initializer for standard types.
///
/// The `store` parameter is useful for unit tests or reading values from other suites.
/// - Parameters:
/// - defaultValue: The default value for the property if the AppConfig value is not set
/// - key: A key into the AppConfig dictionary
/// - defaultValue: The default value for the property if the Managed App Configuration value is not set.
/// - key: A key into the Managed App Configuration dictionary.
/// - store: A `UserDefaults` object; defaults to the `.standard` object if not given.
public init(wrappedValue defaultValue: Value, _ key: String, store: UserDefaults = UserDefaults.standard) {
self.defaultValue = defaultValue
Expand All @@ -58,7 +64,7 @@ import Foundation
///
/// The `store` parameter is useful for unit tests or reading values from other suites.
/// - Parameters:
/// - key: A key into the AppConfig dictionary
/// - key: A key into the Managed App Configuration dictionary.
/// - store: A `UserDefaults` object; defaults to the `.standard` object if not given.
public init(_ key: String, store: UserDefaults = UserDefaults.standard) where Value: ExpressibleByNilLiteral {
self.defaultValue = nil
Expand Down
6 changes: 3 additions & 3 deletions Sources/ManagedAppConfigLib/AppConfigService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ extension Notification.Name {
}

/// An internal service class that keeps track of all used `UserDefaults` objects and listens for changes
/// to managed app config in them.
/// to Managed App Configuration in them.
@available(macOS 11, iOS 7.0, tvOS 10.2, *)
class AppConfigService {
static let shared = AppConfigService()
Expand All @@ -28,14 +28,14 @@ class AppConfigService {
if dictionaries[userDefaults] == nil {
// We haven't seen this user defaults previously;
// load it's AppConfig dictionary and listen for changes to it.
dictionaries[userDefaults] = userDefaults.dictionary(forKey: ManagedAppConfig.defaultsKey) ?? [:]
dictionaries[userDefaults] = userDefaults.dictionary(forKey: ManagedAppConfig.configurationKey) ?? [:]
let center = NotificationCenter.default
observers[userDefaults] = center.addObserver(forName: UserDefaults.didChangeNotification,
object: userDefaults,
queue: .main) { [weak self] (note: Notification) in
guard let self = self else { return }
if let defaults = note.object as? UserDefaults {
let newValues = defaults.dictionary(forKey: ManagedAppConfig.defaultsKey) ?? [:]
let newValues = defaults.dictionary(forKey: ManagedAppConfig.configurationKey) ?? [:]
// Because we can't easily check if specific values in the app config have changed,
// we send notifications when either
// 1) appConfig has been set with some values (maybe new, maybe something changed),
Expand Down
45 changes: 45 additions & 0 deletions Sources/ManagedAppConfigLib/Documentation.docc/AppConfig.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# ``ManagedAppConfigLib/AppConfig``

## Example

Here is an example view that displays some text based on Managed App Configuration. If the
key `title` doesn't exist or is not a String, the property will have the value "Default title".
The subtitle is optional and will only display if the key `subtitle` is present.

```swift
import ManagedAppConfigLib
import SwiftUI

struct ContentView: View {
@AppConfig("title") private var displayTitle = "Default title"
@AppConfig("subtitle") private var subTitle: String?

var body: some View {
VStack {
Text(displayTitle).font(.headline)
if let text = subTitle {
Text(text).font(.subheadline)
}
}
.padding()
}
}
```

## Topics

### Reading a Managed App Configuration value with default

- ``init(wrappedValue:_:store:)``

### Reading an optional Managed App Configuration value

- ``init(_:store:)``

### Getting the value

- ``wrappedValue``

### DynamicProperty Implementation

- ``update()``
31 changes: 31 additions & 0 deletions Sources/ManagedAppConfigLib/Documentation.docc/AppConfigPlain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# ``ManagedAppConfigLib/AppConfigPlain``

## Example

This example has the property wrapper being used in a struct that represents the application
settings. This lets an IT organization override the default title and the starting quantity.

```swift
import ManagedAppConfigLib

struct AppSettings {
@AppConfigPlain("title") var defaultTitle = "Default title"
@AppConfigPlain("quantity") var startingAmount: Int = 0

var actualAmount: Int
}
```

## Topics

### Reading a Managed App Configuration value with default

- ``init(wrappedValue:_:store:)``

### Reading an optional Managed App Configuration value

- ``init(_:store:)``

### Getting the value

- ``wrappedValue``
Loading

0 comments on commit 3fcabd1

Please sign in to comment.