Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
While you’re constructing apps, the entry barrier to some options, together with textual content recognition, is excessive. Even skilled coders take plenty of time and code to get textual content recognition working in video.
DataScannerViewController from the highly effective VisionKit is a self-contained scanner for textual content and barcodes that removes a lot of the difficulties from this job.
If you have to get textual content data into your app, this API is likely to be for you. An inconvenience is that DataScannerViewController is a UIViewController and isn’t immediately uncovered to SwiftUI. That’s OK as a result of UIKit will not be going away quickly. It’s straightforward to mix UIKit and SwiftUI.
On this tutorial, you learn to use and customise DataScannerViewController in a UIKit primarily based app whereas mixing in SwiftUI parts.
To do that tutorial, you want:
Slurpy is an app that makes use of DataScannerViewController to seize textual content and barcodes and retailer them for future use. As an example, a scholar visiting a museum may use Slurpy to seize textual content from exhibit data playing cards for later use.
Obtain the venture utilizing the Obtain Supplies hyperlink on the prime or backside of the tutorial. Open the folder Starter, and open the venture file Slurpy.xcodeproj.
You’ll construct to your system for the tutorial. Join the system to your Mac and choose it because the run vacation spot. The title within the bar would be the title of your system.
Choose the venture file within the Venture navigator:
Construct and run. You see a premade tabbed interface with two tabs “Ingest” and “Use” to maintain you targeted on the cool content material. The next step will probably be so as to add DataScannerViewController to your interface.
On this part, you create and configure the DataScannerViewController from VisionKit and add that to the “Ingest” tab of the interface. You’ll quickly be capable to see what the digicam acknowledges within the view.
Delegate protocols (or the delegation sample) are frequent all by way of the Apple SDKs. They assist you to change a category habits without having to create a subclass.
Within the Venture navigator, within the group ViewControllers, open ScannerViewController.swift. You see an empty class declaration for ScannerViewController
.
Under the road import UIKit
add the import assertion:
import VisionKit
Subsequent, add the next code on the backside of ScannerViewController.swift:
extension ScannerViewController: DataScannerViewControllerDelegate {
func dataScanner(
_ dataScanner: DataScannerViewController,
didAdd addedItems: [RecognizedItem],
allItems: [RecognizedItem]
) {
}
func dataScanner(
_ dataScanner: DataScannerViewController,
didUpdate updatedItems: [RecognizedItem],
allItems: [RecognizedItem]
) {
}
func dataScanner(
_ dataScanner: DataScannerViewController,
didRemove removedItems: [RecognizedItem],
allItems: [RecognizedItem]
) {
}
func dataScanner(
_ dataScanner: DataScannerViewController,
didTapOn merchandise: RecognizedItem
) {
}
}
On this extension, you conform ScannerViewController
to the protocol DataScannerViewControllerDelegate
. DataScannerViewControllerDelegate
has strategies which might be referred to as when DataScannerViewController begins to acknowledge or stops recognizing objects in its area of view.
Come again right here later upon getting the scanner working. For now, this extension should exist to forestall compiler errors.
Subsequent, prolong DataScannerViewController with a perform that instantiates and configures it to your wants.
On this part, make a DataScannerViewController and set it as much as scan textual content and barcodes.
Add this extension on the backside of ScannerViewController.swift:
extension DataScannerViewController {
static func makeDatascanner(delegate: DataScannerViewControllerDelegate)
-> DataScannerViewController {
let scanner = DataScannerViewController(
recognizedDataTypes: [
// restrict the types here later
.text()
],
isGuidanceEnabled: true,
isHighlightingEnabled: true
)
scanner.delegate = delegate
return scanner
}
}
In makeDatascanner
you instantiate DataScannerViewController
. The primary argument to init
, recognizedDataTypes
is an array of RecognizedDataType
objects. The array is empty for now — you’ll add gadgets you wish to acknowledge quickly.
The arguments isGuidanceEnabled
and isHighlightingEnabled
add additional UI to the view that can assist you find objects. Lastly, you make ScannerViewController
the delegate of DataScannerViewController
. This property task connects the DataScannerViewControllerDelegate
strategies you added earlier than.
You’re prepared so as to add the scanner to the view. On the prime of ScannerViewController.swift find the category declaration for ScannerViewController
and add the next inside the category physique:
var datascanner: DataScannerViewController?
Hold a reference to the scanner you create so you can begin and cease the scanner. Subsequent, add this technique to the category physique:
func installDataScanner() {
// 1.
guard datascanner == nil else {
return
}
// add guard right here
// 2.
let scanner = DataScannerViewController.makeDatascanner(delegate: self)
datascanner = scanner
addChild(scanner)
view.pinToInside(scanner.view)
// 3.
addChild(scanner)
scanner.didMove(toParent: self)
// 4.
do {
strive scanner.startScanning()
} catch {
print("** oh no (unable to begin scan) - (error)")
}
}
On this code you:
makeDatascanner
then pin the view of DataScannerViewController
contained in the safeAreaLayoutGuide
space of ScannerViewController
. pinToInside
is an Auto Format helper included with the starter venture.DataScannerViewController
to ScannerViewController
as a baby view controller, then inform the scanner it moved to a mother or father view controller.DataScannerViewController
.The final step is name installDataScanner
when the view seems. Add this code contained in the physique of ScannerViewController
:
override func viewDidAppear(_ animated: Bool) {
tremendous.viewDidAppear(animated)
installDataScanner()
}
You’re prepared to fireside up the app. Construct and run. You see the app instantly crash with a console message much like this:
[access] This app has crashed as a result of it tried to entry privacy-sensitive information with out a utilization description. The app's Information.plist should comprise an NSCameraUsageDescription key with a string worth explaining how the app makes use of this information.
When an app must entry the digicam, it wants to clarify why it ought to be permitted. Add the required key subsequent.
You now want to vary the Information.plist to get your app working.
Construct and run. You see a permission alert seem with the textual content from the digicam utilization description you added.
Contact OK to grant permission. You might have a working digicam.
Level your digicam at some textual content, and also you see a bounding rectangle. This habits is toggled by isHighlightingEnabled
that you simply met earlier.
The default state for DataScannerViewController is to acknowledge every thing it may. That’s enjoyable, but it surely may not be what you need. Within the subsequent part, you’ll learn to restrict DataScannerViewController to solely acknowledge what you want.
A barcode symbology is customary coding for a bit of knowledge. If you happen to encode your information utilizing QR symbology, anyone with a QR reader can decode it.
As an example, your museum or library customer wish to scan some textual content or the ISBN of a guide. An ISBN is a 13-digit quantity. A ISBN ought to use EAN-13 symbology in barcode format. Prohibit your scanning to that sort.
VNBarcodeSymbology
declares all the kinds that you would be able to learn with VisionKit. Amongst these varieties is the EAN-13 customary.
In ScannerViewController
, find makeDatascanner
and discover the remark // add varieties right here
.
Delete the remark, then add this code to the array within the parameter recognizedDataTypes
.barcode(
symbologies: [
.ean13
]),
.textual content(languages: ["en", "pt"])
You might have instructed the DataScannerViewController to search for one sort of barcode and English or Portuguese textual content. Be happy to customise the languages
array with the ISO 639-1 language code on your personal nation.
Construct and run, then scan the barcodes above once more. Discover how Slurpy is faster at locking onto the barcodes and spends much less time leaping round locking onto different gadgets within the area of view.
The UI that DataScannerViewController gives is efficient, however say you need one thing else. Pink is scorching proper now so subsequent you’ll study to make a customized information rectangle.
DataScannerViewController has a property overlayContainerView
. Any views positioned inside this container gained’t intervene with the hit testing within the scanner. This implies you may nonetheless contact gadgets so as to add them to your catalog. Make a SwiftUI primarily based renderer for the acknowledged gadgets you scan.
You’re on the level in your app the place you want a mannequin layer to maintain observe of the objects that DataScannerViewController acknowledges. To avoid wasting time and maintain give attention to the tutorial matter, the starter venture features a easy mannequin layer.
DataScannerViewController makes use of VisionKit.RecognizedItem
to explain an object that it sees.
Within the Venture navigator, open the Mannequin group. Open TransientItem.swift. TransientItem
is a wrapper round RecognizedItem
. You might have this construction so your app will not be depending on the info construction of RecognizedItem
.
The subsequent information construction is StoredItem.swift. StoredItem
is Codable
and could be persevered between periods.
The final file within the Mannequin group is DataStore.swift. DataStore
is an ObservableObject
and a container for each StoredItem
that you simply wish to maintain and TransientItem
that DataScannerViewController acknowledges throughout a scanning session.
DataStore
manages entry to the 2 @Revealed
collections collectedItems
and transientItems
. You’ll plug it into your SwiftUI code later.
Within the subsequent part, you’ll use this mannequin to construct an overlay view.
You’re now able to create that cool Eighties-inspired interface you’ve all the time needed. Within the Venture navigator, choose the Views group.
In Highlighter.swift exchange every thing within Highlighter
with this code:
@EnvironmentObject var datastore: DataStore
var physique: some View {
ForEach(datastore.allTransientItems) { merchandise in
RoundedRectangle(cornerRadius: 4)
.stroke(.pink, lineWidth: 6)
.body(width: merchandise.bounds.width, peak: merchandise.bounds.peak)
.place(x: merchandise.bounds.minX, y: merchandise.bounds.minY)
.overlay(
Picture(systemName: merchandise.icon)
.place(
x: merchandise.bounds.minX,
y: merchandise.bounds.minY - merchandise.bounds.peak / 2 - 20
)
.foregroundColor(.pink)
)
}
}
On this View
you draw a RoundedRectangle
with a pink stroke for every acknowledged merchandise seen. Above the rectangle, you present an icon that exhibits whether or not the merchandise is a barcode or textual content. You’ll see this in motion quickly.
Within the Venture navigator, open the ViewControllers group and open PaintingViewController.swift. Add this import above PaintingViewController
:
import SwiftUI
Add this code inside PaintingViewController
:
override func viewDidLoad() {
tremendous.viewDidLoad()
let paintController = UIHostingController(
rootView: Highlighter().environmentObject(DataStore.shared)
)
paintController.view.backgroundColor = .clear
view.pinToInside(paintController.view)
addChild(paintController)
paintController.didMove(toParent: self)
}
Right here you wrap Highlighter
in a UIHostingController
and inject the shared occasion of DataStore
into the view hierarchy. Use this sample extra instances on this tutorial.
The final sequence for internet hosting a SwiftUI View
in a UIViewController
is:
UIHostingController
on your SwiftUI view.view
of the UIHostingController
to the mother or father UIViewController
.UIHostingController
as a baby of the mother or father UIViewController
.didMove(toParent:)
to inform UIHostingController
of that occasion.Open ScannerViewController.swift once more. Contained in the physique of ScannerViewController
, add the next property under var datascanner: DataScannerViewController?
.
let overlay = PaintingViewController()
Subsequent in makeDataScanner
, find the parameter isHighlightingEnabled
and set it to false
so the default UI doesn’t seem beneath your a lot better model.
Lastly, add this line on the finish of installDataScanner
:
scanner.overlayContainerView.pinToInside(overlay.view)
The Highlighter
view is now a part of the view hierarchy. You’re virtually able to go.
Return to ScannerViewController.swift and find extension ScannerViewController: DataScannerViewControllerDelegate
that you simply added earlier. In that extension are 4 strategies:
The highest technique is:
func dataScanner(
_ dataScanner: DataScannerViewController,
didAdd addedItems: [RecognizedItem],
allItems: [RecognizedItem]
)
This delegate technique is known as when DataScannerViewController begins recognizing an merchandise. Add this code to the physique of dataScanner(_:didAdd:allItems:)
:
DataStore.shared.addThings(
addedItems.map { TransientItem(merchandise: $0) },
allItems: allItems.map { TransientItem(merchandise: $0) }
)
Right here you map every RecognizedItem
to a TransientItem
, then ahead the mapped collections to DataStore
.
Subsequent do an analogous job for dataScanner(_:didUpdate:allItems:)
, which is known as when an merchandise is modified:
Add this code to the physique of dataScanner(_:didUpdate:allItems:)
:
DataStore.shared.updateThings(
updatedItems.map { TransientItem(merchandise: $0) },
allItems: allItems.map { TransientItem(merchandise: $0) }
)
Observe up with the the third delegate dataScanner(_:didRemove:allItems:)
, which is known as when DataScannerViewController stops recognizing an merchandise:
Add this code to the physique of dataScanner(_:didRemove:allItems:)
:
DataStore.shared.removeThings(
removedItems.map { TransientItem(merchandise: $0) },
allItems: allItems.map { TransientItem(merchandise: $0) }
)
The ultimate delegate dataScanner(_:didTapOn:)
is known as whenever you contact the display screen inside a acknowledged area:
Add this line to the physique of dataScanner(_:didTapOn:)
:
DataStore.shared.keepItem(TransientItem(merchandise: merchandise).toStoredItem())
keepItem
makes use of a StoredItem
as a result of you are attempting to persist the thing so you change TransientItem
to StoredItem
utilizing a helper.
In that part, you routed the adjustments from DataScannerViewController to DataStore
, performing all the required mapping on the shopper aspect.
Construct and run to see the brand new hotness.
You now have a scanner able to recording textual content and ISBN numbers. Subsequent, construct a listing to show all of the gadgets you gather.
You’re going to make use of SwiftUI to construct a desk then put that desk within the second tab named “Use” of the applying.
Within the Venture navigator, choose the group Views, then add a brand new SwiftUI View file named ListOfThings.swift.
Delete every thing within ListOfThings
, then add this code inside ListOfThings
:
@EnvironmentObject var datastore: DataStore
var physique: some View {
Record {
ForEach(datastore.collectedItems, id: .id) { merchandise in
// 1.
HStack {
Label(
merchandise.string ?? "<No Textual content>",
systemImage: merchandise.icon
)
Spacer()
ShareLink(merchandise: merchandise.string ?? "") {
Label("", systemImage: "sq..and.arrow.up")
}
}
}
// 2.
.onDelete { indexset in
if let index = indexset.first {
let merchandise = datastore.collectedItems[index]
datastore.deleteItem(merchandise)
}
}
}
}
This code generates a Record
. The desk content material is certain to the @Revealed
array collectedItems
from the DataStore
occasion.
Embed ListOfThings
in a UIHostingController
. Within the Venture navigator, go to the ViewControllers group after which open ListViewController.swift.
Insert this import above ListViewController
:
import SwiftUI
Add this code inside ListViewController
:
override func viewDidLoad() {
tremendous.viewDidLoad()
let datastore = DataStore.shared
let listController = UIHostingController(
rootView: ListOfThings().environmentObject(datastore)
)
view.pinToInside(listController.view)
addChild(listController)
listController.didMove(toParent: self)
}
That’s the identical sample you used when including Highlighter
to the overlay container of DataScannerViewController.
Scan a guide barcode and faucet on the acknowledged area. Additionally, scan a bit of textual content. If you happen to can’t discover any of your individual, you need to use those above. Now whenever you faucet on a acknowledged merchandise it’s added to the info retailer. Swap to the Use tab and also you see the gadgets listed.
Contact any of the gadgets and share the content material utilizing a normal share sheet.
Congratulations! You might have the core of your app all constructed up. You’ll be able to scan barcodes and textual content, and share the scanned content material. The app isn’t fairly customer-ready, so subsequent carry out duties to make it prepared for a wider viewers.
On this part, you’ll deal with some eventualities the place the scanner may not begin. This could occur for 2 fundamental causes.
Take care of dealing with that availability now.
You want some UI to show to customers when their gadgets aren’t supported or out there. You’ll be able to create a general-purpose banner for warning functions.
Within the Venture navigator, choose the group Views and add a brand new SwiftUI View file named FullScreenBanner.swift to the group.
Change every thing inside FullScreenBanner.swift under import SwiftUI
with this code:
struct FullScreenBanner: View {
var systemImageName: String
var mainText: String
var detailText: String
var backgroundColor: Coloration
var physique: some View {
Rectangle()
.fill(backgroundColor)
.overlay(
VStack(spacing: 30) {
Picture(systemName: systemImageName)
.font(.largeTitle)
Textual content(mainText)
.font(.largeTitle)
.multilineTextAlignment(.heart)
Textual content(detailText)
.font(.physique)
.multilineTextAlignment(.heart)
.padding(EdgeInsets(prime: 0, main: 20, backside: 0, trailing: 20))
}
.foregroundColor(.white)
)
.edgesIgnoringSafeArea(.all)
}
}
struct FullScreenBanner_Previews: PreviewProvider {
static var previews: some View {
FullScreenBanner(
systemImageName: "location.circle",
mainText: "Oranges are nice",
detailText: "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
backgroundColor: .cyan
)
}
}
You declare a View
with a vertical stack of 1 picture and two textual content blocks. Show the Preview canvas to see what that appears like:
Now, add a tool help verify to your software logic.
Within the Venture navigator, within the group ViewControllers, open ScannerViewController.swift.
Add this technique and property to ScannerViewController
:
var alertHost: UIViewController?
func cleanHost() {
alertHost?.view.removeFromSuperview()
alertHost = nil
}
In cleanHost
, you take away any beforehand put in view from the view hierarchy of ScannerViewController
.
Add this import under import VisionKit
:
import SwiftUI
Now add these two comparable strategies to ScannerViewController
:
func installNoScanOverlay() {
cleanHost()
let scanNotSupported = FullScreenBanner(
systemImageName: "exclamationmark.octagon.fill",
mainText: "Scanner not supported on this system",
detailText: "You want a tool with a digicam and an A12 Bionic processor or higher (Late 2017)",
backgroundColor: .purple
)
let host = UIHostingController(rootView: scanNotSupported)
view.pinToInside(host.view)
alertHost = host
}
func installNoPermissionOverlay() {
cleanHost()
let noCameraPermission = FullScreenBanner(
systemImageName: "video.slash",
mainText: "Digicam permissions not granted",
detailText: "Go to Settings > Slurpy to grant permission to make use of the digicam",
backgroundColor: .orange
)
let host = UIHostingController(rootView: noCameraPermission)
view.pinToInside(host.view)
alertHost = host
}
These two strategies configure a FullScreenBanner
after which place that View
into the view hierarchy.
Then add this code to ScannerViewController
:
var scanningIsSupported: Bool {
false
// DataScannerViewController.isSupported
}
var scanningIsAvailable: Bool {
DataScannerViewController.isAvailable
}
DataScannerViewController has a static
property isSupported
you need to use to question whether or not the system is updated. For this run-only, you ignore it and return false
so you may check the logic.
Lastly, to forestall a crash, don’t set up the scanner for a nonsupported system.
Find installDataScanner
in ScannerViewController
: On the prime of installDataScanner
add this code on the remark // add guards right here
:
guard scanningIsSupported else {
installNoScanOverlay()
return
}
guard scanningIsAvailable else {
installNoPermissionOverlay()
return
}
These two guards forestall you from instantiating DataScannerViewController, if the preconditions aren’t met. Digicam permissions could be withdrawn at any time by the person and have to be checked each time you wish to begin the digicam.
Construct and run. You see the view ScanNotSupported
as a substitute of the digicam.
Return to var scanningIsSupported
to take away the mock Boolean worth.
false
.DataScannerViewController.isSupported
.Construct and run.
At this level, you may go to Settings > Slurpy in your system and swap off “Enable Slurpy to Entry Digicam” to watch the no permission view in place. If you happen to do, swap permission again on to proceed with the tutorial.
When working with any iOS digicam API, shut the digicam down when you find yourself achieved utilizing it. You begin the scanner in viewDidAppear
so now cease it in viewWillDisappear
. Add this code to ScannerViewController
:
func uninstallDatascanner() {
guard let datascanner else {
return
}
datascanner.stopScanning()
datascanner.view.removeFromSuperview()
datascanner.removeFromParent()
self.datascanner = nil
}
override func viewWillDisappear(_ animated: Bool) {
tremendous.viewWillDisappear(animated)
uninstallDatascanner()
}
In uninstallDatascanner
you cease DataScannerViewController then take away it from the view. You cease utilizing assets that you simply now not want.
While you scanned the guide barcode and a textual content fragment, you in all probability weren’t positive whether or not the merchandise saved. On this part, you’ll present some haptic suggestions to the person after they save an merchandise.
Haptic suggestions is when your system vibrates in response to an motion. Use CoreHaptics
to generate these vibrations.
Add this import on the prime of ScannerViewController
under import SwiftUI
:
import CoreHaptics
Then, add this property to the highest of ScannerViewController
:
let hapticEngine: CHHapticEngine? = {
do {
let engine = strive CHHapticEngine()
engine.notifyWhenPlayersFinished { _ in
return .stopEngine
}
return engine
} catch {
print("haptics are usually not working - as a result of (error)")
return nil
}
}()
Right here you create a CHHapticEngine
and configure it to cease working by returning .stopEngine
as soon as all patterns have performed to the person. Stopping the engine after use is beneficial by the documentation.
Add this extension to ScannerViewController.swift:
extension ScannerViewController {
func hapticPattern() throws -> CHHapticPattern {
let occasions = [
CHHapticEvent(
eventType: .hapticTransient,
parameters: [],
relativeTime: 0,
length: 0.25
),
CHHapticEvent(
eventType: .hapticTransient,
parameters: [],
relativeTime: 0.25,
length: 0.5
)
]
let sample = strive CHHapticPattern(occasions: occasions, parameters: [])
return sample
}
func playHapticClick() {
guard let hapticEngine else {
return
}
guard UIDevice.present.userInterfaceIdiom == .telephone else {
return
}
do {
strive hapticEngine.begin()
let sample = strive hapticPattern()
let participant = strive hapticEngine.makePlayer(with: sample)
strive participant.begin(atTime: 0)
} catch {
print("haptics are usually not working - as a result of (error)")
}
}
}
In hapticPattern
, you construct a CHHapticPattern
that describes a double faucet sample. CHHapticPattern
has a wealthy API that’s value exploring past this tutorial.
playHapticClick
performs your hapticPattern
. Haptics are solely out there on iPhone, so in the event you’re utilizing an iPad, use an early return to do nothing. You’ll quickly do one thing else for iPad.
You begin CHHapticEngine
simply earlier than you play the sample. This connects to the worth .stopEngine
that you simply returned in notifyWhenPlayersFinished
beforehand.
Lastly, find extension ScannerViewController: DataScannerViewControllerDelegate
and add this line on the finish of dataScanner(_:didTapOn:)
:
playHapticClick()
Construct and run to really feel the haptic sample whenever you faucet a acknowledged merchandise. Within the subsequent part, you’ll add a recognition sound for folks utilizing an iPad.
To play a sound, you want a sound file. You may make your individual, however for this tutorial, use a a sound that’s included within the starter venture.
Go to the highest of ScannerViewController.swift and add this import under the opposite imports:
import AVFoundation
Add this property inside ScannerViewController
:
var feedbackPlayer: AVAudioPlayer?
Lastly, add this extension to ScannerViewController.swift:
extension ScannerViewController {
func playFeedbackSound() {
guard let url = Bundle.fundamental.url(
forResource: "WAV_Jinja",
withExtension: "wav"
) else {
return
}
do {
feedbackPlayer = strive AVAudioPlayer(contentsOf: url)
feedbackPlayer?.play()
} catch {
print("Error enjoying sound - (error)!")
}
}
}
Inside dataScanner(_:didTapOn:)
, under playHapticClick()
add this name:
playFeedbackSound()
Construct and run. Be sure that the system isn’t muted and quantity will not be zero. While you contact a acknowledged merchandise, a sound will ring out.
Congratulations. You might have a made a barcode and textual content scanner targeted on college students or librarians who wish to gather ISBN numbers and textual content fragments.
That’s all the fabric for this tutorial, however you may browse the documentation for DataScannerViewController to see different parts of this API.
On this tutorial, you’ll use a UIKit-based venture. If you wish to use DataScannerViewController in a SwiftUI venture, you’ll must host it in a UIViewControllerRepresentable
SwiftUI View
. UIViewControllerRepresentable
is the mirror API of UIHostingViewController
.
UIHostingViewController
.UIViewControllerRepresentable
.Studying how you can implement UIViewControllerRepresentable
is out of scope for this tutorial, however what you’ve realized about DataScannerViewController will apply whenever you do.
You’ll be able to obtain the finished venture utilizing the Obtain Supplies hyperlink on the prime or backside of the tutorial.
On this tutorial you lined:
DataScannerViewController opens up a world of interplay — at minimal growth price — with the bodily and textual worlds.
Have some enjoyable and create a recreation or use it to cover data in plain sight. A QR code can maintain as much as 7,000 characters relying on dimension. If you happen to use encryption solely, folks with the important thing can learn that information. That’s an data channel even when web entry is blocked, unavailable or insecure.
Please share what you develop within the discussion board for this tutorial utilizing the hyperlink under. I look ahead to seeing what you do.