Complete Day 77 Challenge

> Your goal is to build an app that asks users to import a picture from their photo library, then attach a name to whatever they imported.
This commit is contained in:
CypherPoet 2020-01-06 20:44:52 -06:00
parent ddb4f20990
commit b74bdfc9cd
11 changed files with 210 additions and 35 deletions

View File

@ -22,6 +22,8 @@
F36D295F239A071F00095B66 /* CoreDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F36D295E239A071F00095B66 /* CoreDataManager.swift */; };
F36D2965239A0ACA00095B66 /* LocationCollectionMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F36D2963239A0ACA00095B66 /* LocationCollectionMapView.swift */; };
F36D2966239A0ACA00095B66 /* LocationCollectionMapView+Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F36D2964239A0ACA00095B66 /* LocationCollectionMapView+Coordinator.swift */; };
F37071DA23C3E9B900FAC6AB /* UIImagePickerWrapper+Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37071D823C3E9B900FAC6AB /* UIImagePickerWrapper+Coordinator.swift */; };
F37071DB23C3E9B900FAC6AB /* UIImagePickerWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37071D923C3E9B900FAC6AB /* UIImagePickerWrapper.swift */; };
F374DC0A239A431E0016CC65 /* LocationCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F374DC09239A431E0016CC65 /* LocationCollectionView.swift */; };
F374DC14239A4B2C0016CC65 /* Location+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = F374DC12239A4B2B0016CC65 /* Location+CoreDataClass.swift */; };
F374DC15239A4B2C0016CC65 /* Location+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F374DC13239A4B2B0016CC65 /* Location+CoreDataProperties.swift */; };
@ -76,6 +78,8 @@
F36D295E239A071F00095B66 /* CoreDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataManager.swift; sourceTree = "<group>"; };
F36D2963239A0ACA00095B66 /* LocationCollectionMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationCollectionMapView.swift; sourceTree = "<group>"; };
F36D2964239A0ACA00095B66 /* LocationCollectionMapView+Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LocationCollectionMapView+Coordinator.swift"; sourceTree = "<group>"; };
F37071D823C3E9B900FAC6AB /* UIImagePickerWrapper+Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImagePickerWrapper+Coordinator.swift"; sourceTree = "<group>"; };
F37071D923C3E9B900FAC6AB /* UIImagePickerWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImagePickerWrapper.swift; sourceTree = "<group>"; };
F374DC09239A431E0016CC65 /* LocationCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationCollectionView.swift; sourceTree = "<group>"; };
F374DC12239A4B2B0016CC65 /* Location+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Location+CoreDataClass.swift"; sourceTree = "<group>"; };
F374DC13239A4B2B0016CC65 /* Location+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Location+CoreDataProperties.swift"; sourceTree = "<group>"; };
@ -220,6 +224,7 @@
F36D2961239A093D00095B66 /* Views */ = {
isa = PBXGroup;
children = (
F37071D623C3E96400FAC6AB /* UIView Wrappers */,
F36D2962239A0A6900095B66 /* LocationCollectionMapView */,
);
path = Views;
@ -234,6 +239,23 @@
path = LocationCollectionMapView;
sourceTree = "<group>";
};
F37071D623C3E96400FAC6AB /* UIView Wrappers */ = {
isa = PBXGroup;
children = (
F37071D723C3E96D00FAC6AB /* UIImagePickerController */,
);
path = "UIView Wrappers";
sourceTree = "<group>";
};
F37071D723C3E96D00FAC6AB /* UIImagePickerController */ = {
isa = PBXGroup;
children = (
F37071D823C3E9B900FAC6AB /* UIImagePickerWrapper+Coordinator.swift */,
F37071D923C3E9B900FAC6AB /* UIImagePickerWrapper.swift */,
);
path = UIImagePickerController;
sourceTree = "<group>";
};
F374DC08239A42C90016CC65 /* Location Collection */ = {
isa = PBXGroup;
children = (
@ -420,6 +442,7 @@
buildActionMask = 2147483647;
files = (
F394D0A8239FC6E2002E8FFC /* LocationCollectionsListView.swift in Sources */,
F37071DA23C3E9B900FAC6AB /* UIImagePickerWrapper+Coordinator.swift in Sources */,
F394D09C239E77B0002E8FFC /* LocationCollectionsState.swift in Sources */,
F3BF5C1F239D53B700B9F8D8 /* SampleData.swift in Sources */,
F34643C123A5BDBE00033065 /* CurrentApplication.swift in Sources */,
@ -443,6 +466,7 @@
F36D295F239A071F00095B66 /* CoreDataManager.swift in Sources */,
F3C8F65623A837A7008B66A7 /* EditLocationViewModel.swift in Sources */,
F394D0A1239EC7C2002E8FFC /* AppStore.swift in Sources */,
F37071DB23C3E9B900FAC6AB /* UIImagePickerWrapper.swift in Sources */,
F39A41BA23A3E2BB00F3B91D /* Binding+OptionalWrappers.swift in Sources */,
F39A41C023A40C3800F3B91D /* WikipediaAPIService.swift in Sources */,
F34643D923A6D52700033065 /* WikiPage+Comparable.swift in Sources */,

View File

@ -6,8 +6,7 @@
//
//
import Foundation
import SwiftUI
extension Location {
@ -16,6 +15,15 @@ extension Location {
return wikiPages.sorted()
}
var userPhoto: Image? {
guard
let data = userPhotoData,
let uiImage = UIImage(data: data)
else { return nil }
return Image(uiImage: uiImage)
}
}

View File

@ -24,6 +24,8 @@ extension Location {
@NSManaged public var title: String?
@NSManaged public var subtitle: String?
@NSManaged public var longDescription: String?
@NSManaged public var userPhotoData: Data?
@NSManaged public var collection: LocationCollection?
@NSManaged public var wikiPages: NSSet?

View File

@ -6,6 +6,7 @@
<attribute name="longitude" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="subtitle" optional="YES" attributeType="String"/>
<attribute name="title" optional="YES" attributeType="String"/>
<attribute name="userPhotoData" optional="YES" attributeType="Binary" allowsExternalBinaryDataStorage="YES"/>
<relationship name="collection" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LocationCollection" inverseName="locations" inverseEntity="LocationCollection"/>
<relationship name="wikiPages" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="WikiPage" inverseName="location" inverseEntity="WikiPage"/>
<uniquenessConstraints>
@ -37,7 +38,7 @@
</uniquenessConstraints>
</entity>
<elements>
<element name="Location" positionX="-144.60546875" positionY="60.58203125" width="128" height="28"/>
<element name="Location" positionX="-144.60546875" positionY="60.58203125" width="128" height="163"/>
<element name="LocationCollection" positionX="-386.72265625" positionY="-110.62109375" width="128" height="73"/>
<element name="WikiPage" positionX="54.08984375" positionY="264.7734375" width="155.65234375" height="28"/>
</elements>

View File

@ -2,6 +2,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>NSPhotoLibraryUsageDescription</key>
<string>This application would like permission to read and load images from your Photos Album. These can be used to as custom images for a location. </string>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "santorini.jpg",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

View File

@ -0,0 +1,40 @@
//
// UIImagePickerWrapper+Coordinator.swift
// PlaceCase
//
// Created by CypherPoet on 1/6/20.
//
//
import Foundation
import UIKit
extension UIImagePickerWrapper {
class Coordinator: NSObject {
let onSelect: ((UIImage) -> Void)
init(onSelect: @escaping ((UIImage) -> Void)) {
self.onSelect = onSelect
}
}
}
// MARK: - UIImagePickerControllerDelegate
extension UIImagePickerWrapper.Coordinator: UIImagePickerControllerDelegate {
func imagePickerController(
_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]
) {
guard let selectedImage = (info[.editedImage] ?? info[.originalImage]) as? UIImage else {
fatalError()
}
onSelect(selectedImage)
}
}
// MARK: - UINavigationControllerDelegate
extension UIImagePickerWrapper.Coordinator: UINavigationControllerDelegate {}

View File

@ -0,0 +1,56 @@
//
// UIImagePickerWrapper.swift
// PlaceCase
//
// Created by CypherPoet on 1/6/20.
//
//
import SwiftUI
struct UIImagePickerWrapper {
typealias UIViewControllerType = UIImagePickerController
@Environment(\.presentationMode) var presentationMode
let onSelect: ((UIImage) -> Void)
}
// MARK: - UIViewControllerRepresentable
extension UIImagePickerWrapper: UIViewControllerRepresentable {
func makeCoordinator() -> UIImagePickerWrapper.Coordinator {
Self.Coordinator(onSelect: self.imageSelected(_:))
}
func makeUIViewController(
context: UIViewControllerRepresentableContext<UIImagePickerWrapper>
) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(
_ imagePickerController: UIImagePickerController,
context: UIViewControllerRepresentableContext<UIImagePickerWrapper>
) {
}
}
private extension UIImagePickerWrapper {
func imageSelected(_ image: UIImage) {
onSelect(image)
presentationMode.wrappedValue.dismiss()
}
}

View File

@ -14,6 +14,8 @@ struct EditLocationView: View {
@EnvironmentObject var store: AppStore
@ObservedObject var viewModel: EditLocationViewModel
@State private var isShowingImagePicker = false
}
@ -35,34 +37,9 @@ extension EditLocationView {
}
Section(header: Text("Add A Photo").font(.headline)) {
Button(action: {
}) {
Group {
// if viewModel.location.userPhoto != nil {
if false {
} else {
HStack {
Spacer()
VStack {
Image(systemName: "camera")
.resizable()
.scaledToFit()
.frame(width: 120)
Text("No Photo Selected")
.font(.title)
}
Spacer()
}
}
}
.frame(height: 200)
}
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
Section(header: Text("Custom Photo").font(.headline)) {
customPhotoField
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
}
@ -85,20 +62,54 @@ extension EditLocationView {
self.store.send(.wikiPages(.fetchStateSet(.fetching)))
self.store.send(WikiPagesSideEffect.fetchPages(near: self.viewModel.location))
}
.sheet(isPresented: $isShowingImagePicker) {
UIImagePickerWrapper(onSelect: self.userPhotoSelected(_:))
}
}
}
// MARK: - Computeds
extension EditLocationView {
extension EditLocationView {}
}
// MARK: - View Variables
extension EditLocationView {
private var customPhotoField: some View {
Button(action: {
self.isShowingImagePicker = true
}) {
HStack {
Group {
if viewModel.location.userPhoto != nil {
viewModel.location.userPhoto!
.renderingMode(.original)
.resizable()
.scaledToFill()
.frame(height: 200)
.clipped()
} else {
Spacer()
VStack {
Image(systemName: "camera")
.resizable()
.scaledToFit()
.frame(width: 120)
Text("Add a Photo")
.font(.title)
}
Spacer()
}
}
}
.frame(height: 200)
}
}
private var saveButton: some View {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
@ -109,6 +120,16 @@ extension EditLocationView {
}
// MARK: - Private Helpers
private extension EditLocationView {
func userPhotoSelected(_ uiImage: UIImage) {
viewModel.location.userPhotoData = uiImage.pngData()
}
}
// MARK: - Preview
struct EditLocationView_Previews: PreviewProvider {

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB