Archived
1
Fork 0

Add initial files

This commit is contained in:
redstrate 2020-02-18 06:57:35 -05:00
commit 52e6ffc8d0
26 changed files with 2208 additions and 0 deletions

View file

@ -0,0 +1,406 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
03093523235660E100E44910 /* ReverseImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03093522235660E100E44910 /* ReverseImageViewController.swift */; };
030935252356833A00E44910 /* InfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030935242356833A00E44910 /* InfoViewController.swift */; };
0361A4F8234690C000639E67 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0361A4F7234690C000639E67 /* AppDelegate.swift */; };
0361A4FA234690C000639E67 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0361A4F9234690C000639E67 /* SceneDelegate.swift */; };
0361A4FC234690C000639E67 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0361A4FB234690C000639E67 /* ViewController.swift */; };
0361A4FF234690C000639E67 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0361A4FD234690C000639E67 /* Main.storyboard */; };
0361A502234690C000639E67 /* Gallery.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 0361A500234690C000639E67 /* Gallery.xcdatamodeld */; };
0361A504234690C100639E67 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0361A503234690C100639E67 /* Assets.xcassets */; };
0361A507234690C100639E67 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0361A505234690C100639E67 /* LaunchScreen.storyboard */; };
0361A5102346919A00639E67 /* PostViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0361A50F2346919A00639E67 /* PostViewCell.swift */; };
0361A5122346A38100639E67 /* PostViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0361A5112346A38100639E67 /* PostViewController.swift */; };
0361A514234828D000639E67 /* EditTagsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0361A513234828D000639E67 /* EditTagsViewController.swift */; };
0361A51623482A7D00639E67 /* TagViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0361A51523482A7D00639E67 /* TagViewCell.swift */; };
03F92BA22349635C0000DC1C /* PostCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03F92BA12349635C0000DC1C /* PostCollectionView.swift */; };
03F92BA4234967E00000DC1C /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03F92BA3234967E00000DC1C /* SearchViewController.swift */; };
03F92BA6234969800000DC1C /* TagViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03F92BA5234969800000DC1C /* TagViewController.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
03093522235660E100E44910 /* ReverseImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReverseImageViewController.swift; sourceTree = "<group>"; };
030935242356833A00E44910 /* InfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoViewController.swift; sourceTree = "<group>"; };
0361A4F4234690C000639E67 /* Gallery.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Gallery.app; sourceTree = BUILT_PRODUCTS_DIR; };
0361A4F7234690C000639E67 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
0361A4F9234690C000639E67 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
0361A4FB234690C000639E67 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
0361A4FE234690C000639E67 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
0361A501234690C000639E67 /* Gallery.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Gallery.xcdatamodel; sourceTree = "<group>"; };
0361A503234690C100639E67 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
0361A506234690C100639E67 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
0361A508234690C100639E67 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0361A50E234690F200639E67 /* Gallery.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Gallery.entitlements; sourceTree = "<group>"; };
0361A50F2346919A00639E67 /* PostViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostViewCell.swift; sourceTree = "<group>"; };
0361A5112346A38100639E67 /* PostViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostViewController.swift; sourceTree = "<group>"; };
0361A513234828D000639E67 /* EditTagsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditTagsViewController.swift; sourceTree = "<group>"; };
0361A51523482A7D00639E67 /* TagViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagViewCell.swift; sourceTree = "<group>"; };
03F92BA12349635C0000DC1C /* PostCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostCollectionView.swift; sourceTree = "<group>"; };
03F92BA3234967E00000DC1C /* SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = "<group>"; };
03F92BA5234969800000DC1C /* TagViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagViewController.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
0361A4F1234690C000639E67 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0361A4EB234690C000639E67 = {
isa = PBXGroup;
children = (
0361A4F6234690C000639E67 /* Gallery */,
0361A4F5234690C000639E67 /* Products */,
);
sourceTree = "<group>";
};
0361A4F5234690C000639E67 /* Products */ = {
isa = PBXGroup;
children = (
0361A4F4234690C000639E67 /* Gallery.app */,
);
name = Products;
sourceTree = "<group>";
};
0361A4F6234690C000639E67 /* Gallery */ = {
isa = PBXGroup;
children = (
0361A50E234690F200639E67 /* Gallery.entitlements */,
0361A4F7234690C000639E67 /* AppDelegate.swift */,
0361A4F9234690C000639E67 /* SceneDelegate.swift */,
0361A4FB234690C000639E67 /* ViewController.swift */,
0361A4FD234690C000639E67 /* Main.storyboard */,
0361A503234690C100639E67 /* Assets.xcassets */,
0361A505234690C100639E67 /* LaunchScreen.storyboard */,
0361A508234690C100639E67 /* Info.plist */,
0361A500234690C000639E67 /* Gallery.xcdatamodeld */,
0361A50F2346919A00639E67 /* PostViewCell.swift */,
0361A5112346A38100639E67 /* PostViewController.swift */,
0361A513234828D000639E67 /* EditTagsViewController.swift */,
0361A51523482A7D00639E67 /* TagViewCell.swift */,
03F92BA12349635C0000DC1C /* PostCollectionView.swift */,
03F92BA3234967E00000DC1C /* SearchViewController.swift */,
03F92BA5234969800000DC1C /* TagViewController.swift */,
03093522235660E100E44910 /* ReverseImageViewController.swift */,
030935242356833A00E44910 /* InfoViewController.swift */,
);
path = Gallery;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
0361A4F3234690C000639E67 /* Gallery */ = {
isa = PBXNativeTarget;
buildConfigurationList = 0361A50B234690C100639E67 /* Build configuration list for PBXNativeTarget "Gallery" */;
buildPhases = (
0361A4F0234690C000639E67 /* Sources */,
0361A4F1234690C000639E67 /* Frameworks */,
0361A4F2234690C000639E67 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = Gallery;
productName = Gallery;
productReference = 0361A4F4234690C000639E67 /* Gallery.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
0361A4EC234690C000639E67 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1100;
LastUpgradeCheck = 1100;
ORGANIZATIONNAME = "Joshua Goins";
TargetAttributes = {
0361A4F3234690C000639E67 = {
CreatedOnToolsVersion = 11.0;
};
};
};
buildConfigurationList = 0361A4EF234690C000639E67 /* Build configuration list for PBXProject "Gallery" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 0361A4EB234690C000639E67;
productRefGroup = 0361A4F5234690C000639E67 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
0361A4F3234690C000639E67 /* Gallery */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
0361A4F2234690C000639E67 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0361A507234690C100639E67 /* LaunchScreen.storyboard in Resources */,
0361A504234690C100639E67 /* Assets.xcassets in Resources */,
0361A4FF234690C000639E67 /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
0361A4F0234690C000639E67 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
03F92BA4234967E00000DC1C /* SearchViewController.swift in Sources */,
03F92BA22349635C0000DC1C /* PostCollectionView.swift in Sources */,
0361A5122346A38100639E67 /* PostViewController.swift in Sources */,
0361A502234690C000639E67 /* Gallery.xcdatamodeld in Sources */,
0361A4FC234690C000639E67 /* ViewController.swift in Sources */,
0361A51623482A7D00639E67 /* TagViewCell.swift in Sources */,
03093523235660E100E44910 /* ReverseImageViewController.swift in Sources */,
03F92BA6234969800000DC1C /* TagViewController.swift in Sources */,
0361A4F8234690C000639E67 /* AppDelegate.swift in Sources */,
030935252356833A00E44910 /* InfoViewController.swift in Sources */,
0361A514234828D000639E67 /* EditTagsViewController.swift in Sources */,
0361A4FA234690C000639E67 /* SceneDelegate.swift in Sources */,
0361A5102346919A00639E67 /* PostViewCell.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
0361A4FD234690C000639E67 /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
0361A4FE234690C000639E67 /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
0361A505234690C100639E67 /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
0361A506234690C100639E67 /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
0361A509234690C100639E67 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
0361A50A234690C100639E67 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.1;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
0361A50C234690C100639E67 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = Gallery/Gallery.entitlements;
CODE_SIGN_STYLE = Automatic;
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
DEVELOPMENT_TEAM = JM5LKVKH48;
INFOPLIST_FILE = Gallery/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = jorg.Gallery;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
0361A50D234690C100639E67 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = Gallery/Gallery.entitlements;
CODE_SIGN_STYLE = Automatic;
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
DEVELOPMENT_TEAM = JM5LKVKH48;
INFOPLIST_FILE = Gallery/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = jorg.Gallery;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
0361A4EF234690C000639E67 /* Build configuration list for PBXProject "Gallery" */ = {
isa = XCConfigurationList;
buildConfigurations = (
0361A509234690C100639E67 /* Debug */,
0361A50A234690C100639E67 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
0361A50B234690C100639E67 /* Build configuration list for PBXNativeTarget "Gallery" */ = {
isa = XCConfigurationList;
buildConfigurations = (
0361A50C234690C100639E67 /* Debug */,
0361A50D234690C100639E67 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCVersionGroup section */
0361A500234690C000639E67 /* Gallery.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
0361A501234690C000639E67 /* Gallery.xcdatamodel */,
);
currentVersion = 0361A501234690C000639E67 /* Gallery.xcdatamodel */;
path = Gallery.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;
};
/* End XCVersionGroup section */
};
rootObject = 0361A4EC234690C000639E67 /* Project object */;
}

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:Gallery.xcodeproj">
</FileRef>
</Workspace>

View file

@ -0,0 +1,8 @@
<?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>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

40
Gallery/AppDelegate.swift Normal file
View file

@ -0,0 +1,40 @@
import UIKit
import CoreData
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
}
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Gallery")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}

View file

@ -0,0 +1,98 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View file

@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>

View file

@ -0,0 +1,485 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15400" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Q6f-oJ-eRP">
<device id="mac" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15404"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="collection view cell content view" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Navigation Controller-->
<scene sceneID="jm1-yw-Rkx">
<objects>
<navigationController id="Q6f-oJ-eRP" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="bva-S3-Kph">
<rect key="frame" x="0.0" y="0.0" width="800" height="50"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<connections>
<segue destination="BYZ-38-t0r" kind="relationship" relationship="rootViewController" id="ukP-91-aOn"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="snU-Pv-qdo" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-643" y="89"/>
</scene>
<!--Home-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="Gallery" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="nHo-hC-uT9" customClass="PostCollectionView" customModule="Gallery" customModuleProvider="target">
<rect key="frame" x="0.0" y="50" width="800" height="550"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="N2K-Me-YT7">
<size key="itemSize" width="50" height="50"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="postCell" id="eZg-2I-ubl" customClass="PostViewCell" customModule="Gallery" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<collectionViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="69R-kF-95b">
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="phY-ea-yfA">
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</imageView>
</subviews>
</collectionViewCellContentView>
<connections>
<outlet property="imageView" destination="phY-ea-yfA" id="1c7-5d-yIu"/>
<segue destination="jQf-RA-c2I" kind="show" identifier="showPost" id="s4Q-Cr-1uF"/>
</connections>
</collectionViewCell>
</cells>
</collectionView>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="nHo-hC-uT9" firstAttribute="centerY" secondItem="8bC-Xf-vdC" secondAttribute="centerY" constant="25" id="715-Gf-Neb"/>
<constraint firstItem="nHo-hC-uT9" firstAttribute="top" secondItem="6Tk-OE-BBY" secondAttribute="top" id="PKo-Re-WQy"/>
<constraint firstItem="nHo-hC-uT9" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="UdF-XX-6F1"/>
<constraint firstItem="nHo-hC-uT9" firstAttribute="trailing" secondItem="8bC-Xf-vdC" secondAttribute="trailingMargin" constant="20" id="ZpI-FT-bld"/>
</constraints>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
<navigationItem key="navigationItem" title="Home" id="jQ3-8r-PXH">
<barButtonItem key="leftBarButtonItem" systemItem="add" id="7SV-nk-ZV3">
<connections>
<action selector="importAction:" destination="BYZ-38-t0r" id="dZW-hp-FWf"/>
</connections>
</barButtonItem>
<barButtonItem key="rightBarButtonItem" systemItem="search" id="6aR-5j-kcX">
<connections>
<segue destination="E6A-NM-9w9" kind="show" identifier="search" id="CBy-4f-b7S"/>
</connections>
</barButtonItem>
</navigationItem>
<connections>
<outlet property="collectionView" destination="nHo-hC-uT9" id="mmZ-7N-1va"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="132.75" y="89"/>
</scene>
<!--Post-->
<scene sceneID="El6-Kv-Gbq">
<objects>
<viewController storyboardIdentifier="PostViewController" useStoryboardIdentifierAsRestorationIdentifier="YES" id="jQf-RA-c2I" customClass="PostViewController" customModule="Gallery" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8Jm-eh-tzq">
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView clipsSubviews="YES" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="7Hd-Ax-kkQ">
<rect key="frame" x="0.0" y="50" width="800" height="550"/>
<gestureRecognizers/>
<connections>
<outletCollection property="gestureRecognizers" destination="uQa-Iu-LrE" appends="YES" id="Qec-Ks-5Aj"/>
</connections>
</imageView>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<gestureRecognizers/>
<constraints>
<constraint firstItem="7Hd-Ax-kkQ" firstAttribute="bottom" secondItem="NYh-YZ-QnJ" secondAttribute="bottom" id="3hq-ul-aHb"/>
<constraint firstItem="7Hd-Ax-kkQ" firstAttribute="centerX" secondItem="8Jm-eh-tzq" secondAttribute="centerX" id="GEN-bq-TDj"/>
<constraint firstItem="7Hd-Ax-kkQ" firstAttribute="leading" secondItem="NYh-YZ-QnJ" secondAttribute="leading" id="Lek-GS-1nx"/>
<constraint firstItem="7Hd-Ax-kkQ" firstAttribute="top" secondItem="NYh-YZ-QnJ" secondAttribute="top" id="Pz5-1j-hxY"/>
</constraints>
<viewLayoutGuide key="safeArea" id="NYh-YZ-QnJ"/>
</view>
<navigationItem key="navigationItem" title="Post" id="zrg-Ib-oQd">
<rightBarButtonItems>
<barButtonItem title="Item" image="square.and.arrow.up" catalog="system" id="NNI-g2-aTY">
<connections>
<action selector="shareAction:" destination="jQf-RA-c2I" id="H7r-Iw-ZQ6"/>
</connections>
</barButtonItem>
<barButtonItem title="Tags" image="pencil" catalog="system" id="8d3-sz-ihb">
<connections>
<segue destination="3TL-qD-pbO" kind="popoverPresentation" identifier="showTags" popoverAnchorBarButtonItem="8d3-sz-ihb" id="QRP-ua-ucM">
<popoverArrowDirection key="popoverArrowDirection" up="YES" down="YES" left="YES" right="YES"/>
</segue>
</connections>
</barButtonItem>
<barButtonItem title="Info" id="4FI-KY-8yL">
<connections>
<segue destination="MaR-nc-wxI" kind="popoverPresentation" identifier="showInfo" popoverAnchorBarButtonItem="4FI-KY-8yL" id="OSs-lm-jsP">
<popoverArrowDirection key="popoverArrowDirection" up="YES" down="YES" left="YES" right="YES"/>
</segue>
</connections>
</barButtonItem>
</rightBarButtonItems>
</navigationItem>
<connections>
<outlet property="imageView" destination="7Hd-Ax-kkQ" id="Gq6-1m-LOs"/>
<outlet property="shareButton" destination="NNI-g2-aTY" id="Kji-Gj-9pN"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="VII-uM-HmI" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
<tapGestureRecognizer id="uQa-Iu-LrE">
<connections>
<action selector="playAction:" destination="jQf-RA-c2I" id="SfZ-fD-BfC"/>
</connections>
</tapGestureRecognizer>
</objects>
<point key="canvasLocation" x="998" y="89"/>
</scene>
<!--Edit Tags View Controller-->
<scene sceneID="xfh-Ag-H7b">
<objects>
<viewController storyboardIdentifier="EditTagsViewController" useStoryboardIdentifierAsRestorationIdentifier="YES" id="3TL-qD-pbO" customClass="EditTagsViewController" customModule="Gallery" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="FH2-sj-H4t">
<rect key="frame" x="0.0" y="0.0" width="333" height="480"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="bb4-UL-VAQ">
<rect key="frame" x="29" y="111" width="288" height="353"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="tagCell" id="c7T-0r-L5w" customClass="TagViewCell" customModule="Gallery" customModuleProvider="target">
<rect key="frame" x="0.0" y="28" width="288" height="44.5"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="c7T-0r-L5w" id="WjD-Nb-tif">
<rect key="frame" x="0.0" y="0.0" width="288" height="44.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Cei-95-iOR">
<rect key="frame" x="15" y="12" width="258" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstItem="Cei-95-iOR" firstAttribute="trailing" secondItem="WjD-Nb-tif" secondAttribute="trailingMargin" id="VaJ-GJ-hFJ"/>
<constraint firstItem="Cei-95-iOR" firstAttribute="top" secondItem="WjD-Nb-tif" secondAttribute="topMargin" constant="2" id="f84-FI-m0u"/>
<constraint firstItem="Cei-95-iOR" firstAttribute="leading" secondItem="WjD-Nb-tif" secondAttribute="leadingMargin" id="ggu-hW-gB3"/>
<constraint firstItem="Cei-95-iOR" firstAttribute="centerY" secondItem="WjD-Nb-tif" secondAttribute="centerY" id="vzg-jU-quQ"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="label" destination="Cei-95-iOR" id="gA4-H1-RJF"/>
<segue destination="1Lq-DE-RSc" kind="show" identifier="showTag" id="WEP-it-u9v"/>
</connections>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="3TL-qD-pbO" id="qkX-W1-3Rb"/>
<outlet property="delegate" destination="3TL-qD-pbO" id="BK9-Bn-TJy"/>
</connections>
</tableView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Tags" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Avc-oj-cgH">
<rect key="frame" x="148" y="21" width="38" height="20"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="vru-rx-keE">
<rect key="frame" x="280" y="15" width="38" height="31"/>
<state key="normal" title="Done"/>
<connections>
<action selector="doneAction:" destination="3TL-qD-pbO" eventType="touchDown" id="qAw-6K-ZZF"/>
</connections>
</button>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Enter new tag..." textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="2po-UQ-V0K">
<rect key="frame" x="29" y="61" width="288" height="34"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
<connections>
<action selector="editingFinished:" destination="3TL-qD-pbO" eventType="editingDidEnd" id="MGp-b4-ufV"/>
</connections>
</textField>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="iWK-vF-Vze">
<rect key="frame" x="29" y="15" width="30" height="31"/>
<state key="normal" title="Edit"/>
<connections>
<action selector="editAction:" destination="3TL-qD-pbO" eventType="touchDown" id="5er-HM-8S2"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="Avc-oj-cgH" firstAttribute="centerX" secondItem="FH2-sj-H4t" secondAttribute="centerX" id="0Gc-7b-iOz"/>
<constraint firstItem="iWK-vF-Vze" firstAttribute="top" secondItem="QZA-mC-Y5U" secondAttribute="top" constant="15" id="1GH-Ph-jLQ"/>
<constraint firstItem="2po-UQ-V0K" firstAttribute="top" secondItem="iWK-vF-Vze" secondAttribute="bottom" constant="15" id="Tfw-tD-NIh"/>
<constraint firstItem="bb4-UL-VAQ" firstAttribute="top" secondItem="2po-UQ-V0K" secondAttribute="bottom" constant="16" id="Tza-Yu-GJy"/>
<constraint firstItem="vru-rx-keE" firstAttribute="top" secondItem="QZA-mC-Y5U" secondAttribute="top" constant="15" id="Vie-or-57j"/>
<constraint firstItem="QZA-mC-Y5U" firstAttribute="trailing" secondItem="bb4-UL-VAQ" secondAttribute="trailing" constant="16" id="Y0t-Xb-GeO"/>
<constraint firstItem="bb4-UL-VAQ" firstAttribute="leading" secondItem="QZA-mC-Y5U" secondAttribute="leading" constant="16" id="dU7-AM-rdu"/>
<constraint firstItem="QZA-mC-Y5U" firstAttribute="trailing" secondItem="vru-rx-keE" secondAttribute="trailing" constant="15" id="fQA-zb-Ggk"/>
<constraint firstItem="2po-UQ-V0K" firstAttribute="leading" secondItem="QZA-mC-Y5U" secondAttribute="leading" constant="16" id="h3a-gQ-AeR"/>
<constraint firstItem="iWK-vF-Vze" firstAttribute="leading" secondItem="QZA-mC-Y5U" secondAttribute="leading" constant="16" id="hJB-8r-k89"/>
<constraint firstItem="QZA-mC-Y5U" firstAttribute="trailing" secondItem="2po-UQ-V0K" secondAttribute="trailing" constant="16" id="mcC-aj-xcR"/>
<constraint firstItem="QZA-mC-Y5U" firstAttribute="bottom" secondItem="bb4-UL-VAQ" secondAttribute="bottom" constant="16" id="o9M-an-8qu"/>
<constraint firstItem="Avc-oj-cgH" firstAttribute="top" secondItem="QZA-mC-Y5U" secondAttribute="top" constant="21" id="sTL-Nv-S4n"/>
</constraints>
<viewLayoutGuide key="safeArea" id="QZA-mC-Y5U"/>
</view>
<connections>
<outlet property="tableView" destination="bb4-UL-VAQ" id="Vq5-Vb-i3L"/>
<outlet property="tagField" destination="2po-UQ-V0K" id="Mrv-6H-7QS"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="tmO-Po-quZ" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="997.5" y="850"/>
</scene>
<!--Info View Controller-->
<scene sceneID="w3u-pt-BtZ">
<objects>
<viewController storyboardIdentifier="InfoViewController" useStoryboardIdentifierAsRestorationIdentifier="YES" id="MaR-nc-wxI" customClass="InfoViewController" customModule="Gallery" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="VFL-0t-HSi">
<rect key="frame" x="0.0" y="0.0" width="333" height="480"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yPq-yv-LyM">
<rect key="frame" x="279" y="15" width="38" height="30"/>
<state key="normal" title="Done"/>
<connections>
<action selector="exitAction:" destination="MaR-nc-wxI" eventType="touchDown" id="VYZ-ym-Qcz"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" numberOfLines="5" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="NDx-zx-hmw">
<rect key="frame" x="33" y="57" width="280" height="423"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="NDx-zx-hmw" firstAttribute="leading" secondItem="lo4-uV-JuH" secondAttribute="leading" constant="20" id="8qu-VF-O1C"/>
<constraint firstItem="lo4-uV-JuH" firstAttribute="trailing" secondItem="yPq-yv-LyM" secondAttribute="trailing" constant="16" id="eL9-wr-Ckn"/>
<constraint firstItem="yPq-yv-LyM" firstAttribute="top" secondItem="lo4-uV-JuH" secondAttribute="top" constant="15" id="hgP-Wl-Ugt"/>
<constraint firstItem="NDx-zx-hmw" firstAttribute="top" secondItem="yPq-yv-LyM" secondAttribute="bottom" constant="12" id="jtW-aZ-fN6"/>
<constraint firstItem="lo4-uV-JuH" firstAttribute="trailing" secondItem="NDx-zx-hmw" secondAttribute="trailing" constant="20" id="lA8-3s-iso"/>
<constraint firstItem="NDx-zx-hmw" firstAttribute="bottom" secondItem="lo4-uV-JuH" secondAttribute="bottom" id="uGJ-74-Xc1"/>
<constraint firstItem="NDx-zx-hmw" firstAttribute="top" secondItem="lo4-uV-JuH" secondAttribute="top" constant="57" id="yYl-PH-q7f"/>
</constraints>
<viewLayoutGuide key="safeArea" id="lo4-uV-JuH"/>
</view>
<connections>
<outlet property="infoLabel" destination="NDx-zx-hmw" id="S1H-PB-0sL"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="MNn-jG-FmI" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="330.43478260869568" y="1633.9285714285713"/>
</scene>
<!--Reverse Image View Controller-->
<scene sceneID="Si7-Hr-Nu4">
<objects>
<viewController storyboardIdentifier="ReverseImageViewController" useStoryboardIdentifierAsRestorationIdentifier="YES" id="VhH-X2-v6b" customClass="ReverseImageViewController" customModule="Gallery" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="TNu-lY-cd3">
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<wkWebView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uq3-56-0hX">
<rect key="frame" x="0.0" y="59" width="800" height="541"/>
<color key="backgroundColor" red="0.36078431370000003" green="0.38823529410000002" blue="0.4039215686" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<wkWebViewConfiguration key="configuration">
<audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
<wkPreferences key="preferences"/>
</wkWebViewConfiguration>
</wkWebView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="rUi-3B-MYc">
<rect key="frame" x="20" y="20" width="49" height="31"/>
<state key="normal" title="Cancel"/>
<connections>
<action selector="cancelAction:" destination="VhH-X2-v6b" eventType="touchDown" id="ggg-rw-euW"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dtc-GD-q7J">
<rect key="frame" x="734" y="20" width="46" height="31"/>
<constraints>
<constraint firstAttribute="width" constant="46" id="jzo-YK-PFI"/>
</constraints>
<state key="normal" title="Done"/>
</button>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="rUi-3B-MYc" firstAttribute="top" secondItem="ZJg-bs-auM" secondAttribute="top" constant="20" id="9rH-KP-fyh"/>
<constraint firstItem="uq3-56-0hX" firstAttribute="top" secondItem="dtc-GD-q7J" secondAttribute="bottom" constant="8" id="Rrv-D5-qvg"/>
<constraint firstItem="uq3-56-0hX" firstAttribute="leading" secondItem="ZJg-bs-auM" secondAttribute="leading" id="Si3-C6-jg1"/>
<constraint firstItem="uq3-56-0hX" firstAttribute="bottom" secondItem="ZJg-bs-auM" secondAttribute="bottom" id="TqS-rI-R3m"/>
<constraint firstItem="uq3-56-0hX" firstAttribute="centerX" secondItem="TNu-lY-cd3" secondAttribute="centerX" id="XC5-nd-kiz"/>
<constraint firstItem="rUi-3B-MYc" firstAttribute="leading" secondItem="TNu-lY-cd3" secondAttribute="leadingMargin" id="m4y-yq-AIi"/>
<constraint firstItem="rUi-3B-MYc" firstAttribute="baseline" secondItem="dtc-GD-q7J" secondAttribute="baseline" id="pgb-bf-IaK"/>
<constraint firstItem="dtc-GD-q7J" firstAttribute="trailing" secondItem="TNu-lY-cd3" secondAttribute="trailingMargin" id="tkL-yp-n5c"/>
</constraints>
<viewLayoutGuide key="safeArea" id="ZJg-bs-auM"/>
</view>
<connections>
<outlet property="webView" destination="uq3-56-0hX" id="5LV-9f-ZMn"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="v6u-Ow-alL" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="997.5" y="1634"/>
</scene>
<!--Search View Controller-->
<scene sceneID="Uih-gS-AlG">
<objects>
<viewController id="E6A-NM-9w9" customClass="SearchViewController" customModule="Gallery" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="ZwQ-d4-u0T">
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="Bba-zG-RTo" customClass="PostCollectionView" customModule="Gallery" customModuleProvider="target">
<rect key="frame" x="0.0" y="102" width="800" height="498"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<collectionViewFlowLayout key="collectionViewLayout" automaticEstimatedItemSize="YES" minimumLineSpacing="10" minimumInteritemSpacing="10" id="aRU-y0-ffy">
<size key="itemSize" width="50" height="50"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="postCell" id="hUs-QH-sBl" customClass="PostViewCell" customModule="Gallery" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<collectionViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="Epj-EF-X2t">
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dNV-SX-dFx">
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</imageView>
</subviews>
</collectionViewCellContentView>
<connections>
<outlet property="imageView" destination="dNV-SX-dFx" id="IqG-SD-Dog"/>
<segue destination="jQf-RA-c2I" kind="show" identifier="showPost" id="ofA-CT-ukd"/>
</connections>
</collectionViewCell>
</cells>
</collectionView>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Search..." textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="hMR-QW-233">
<rect key="frame" x="20" y="60" width="760" height="34"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
<connections>
<action selector="searchAction:" destination="E6A-NM-9w9" eventType="editingDidEnd" id="2xV-et-aTt"/>
</connections>
</textField>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="Bba-zG-RTo" firstAttribute="bottom" secondItem="XAJ-Us-ebW" secondAttribute="bottom" id="CKC-xg-anw"/>
<constraint firstItem="hMR-QW-233" firstAttribute="top" secondItem="XAJ-Us-ebW" secondAttribute="top" constant="10" id="JgY-nz-Twd"/>
<constraint firstItem="hMR-QW-233" firstAttribute="leading" secondItem="ZwQ-d4-u0T" secondAttribute="leadingMargin" id="YYK-cm-BvJ"/>
<constraint firstItem="Bba-zG-RTo" firstAttribute="top" secondItem="hMR-QW-233" secondAttribute="bottom" constant="8" id="lBc-kc-K2A"/>
<constraint firstItem="Bba-zG-RTo" firstAttribute="centerX" secondItem="hMR-QW-233" secondAttribute="centerX" id="ny9-KE-3qb"/>
<constraint firstItem="Bba-zG-RTo" firstAttribute="leading" secondItem="XAJ-Us-ebW" secondAttribute="leading" id="tWf-Qt-3Pe"/>
<constraint firstItem="Bba-zG-RTo" firstAttribute="centerX" secondItem="ZwQ-d4-u0T" secondAttribute="centerX" id="xrL-uW-uAg"/>
</constraints>
<viewLayoutGuide key="safeArea" id="XAJ-Us-ebW"/>
</view>
<navigationItem key="navigationItem" id="Qx5-wh-yg8"/>
<connections>
<outlet property="collectionView" destination="Bba-zG-RTo" id="udm-J1-pcl"/>
<outlet property="searchField" destination="hMR-QW-233" id="apd-Ge-h1s"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="rr3-fO-vUC" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="132.75" y="850"/>
</scene>
<!--Tag View Controller-->
<scene sceneID="3aP-dA-v7Z">
<objects>
<viewController id="1Lq-DE-RSc" customClass="TagViewController" customModule="Gallery" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="YEa-YJ-CTQ">
<rect key="frame" x="0.0" y="0.0" width="720" height="560"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="5op-NT-lmm" customClass="PostCollectionView" customModule="Gallery" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="720" height="560"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<collectionViewFlowLayout key="collectionViewLayout" automaticEstimatedItemSize="YES" minimumLineSpacing="10" minimumInteritemSpacing="10" id="d7Y-hv-w27">
<size key="itemSize" width="50" height="50"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="postCell" id="dEY-Wd-6C8" customClass="PostViewCell" customModule="Gallery" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<collectionViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="L9Y-GL-V4q">
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="zR3-iK-0nS">
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</imageView>
</subviews>
</collectionViewCellContentView>
<connections>
<outlet property="imageView" destination="zR3-iK-0nS" id="Nyl-KY-G07"/>
</connections>
</collectionViewCell>
</cells>
</collectionView>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="5op-NT-lmm" firstAttribute="leading" secondItem="kWa-6F-UbO" secondAttribute="leading" id="4IM-cW-hgG"/>
<constraint firstItem="5op-NT-lmm" firstAttribute="top" secondItem="kWa-6F-UbO" secondAttribute="top" id="asN-7m-A2j"/>
<constraint firstItem="5op-NT-lmm" firstAttribute="bottom" secondItem="kWa-6F-UbO" secondAttribute="bottom" id="gF4-Hd-rgB"/>
<constraint firstItem="5op-NT-lmm" firstAttribute="centerX" secondItem="YEa-YJ-CTQ" secondAttribute="centerX" id="kTe-tp-KLp"/>
</constraints>
<viewLayoutGuide key="safeArea" id="kWa-6F-UbO"/>
</view>
<navigationItem key="navigationItem" id="X08-yv-ta4"/>
<connections>
<outlet property="collectionView" destination="5op-NT-lmm" id="0rb-w4-kqs"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iEs-Vf-Pai" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1747.5" y="497"/>
</scene>
</scenes>
<resources>
<image name="pencil" catalog="system" width="64" height="56"/>
<image name="square.and.arrow.up" catalog="system" width="56" height="64"/>
</resources>
<inferredMetricsTieBreakers>
<segue reference="s4Q-Cr-1uF"/>
</inferredMetricsTieBreakers>
</document>

View file

@ -0,0 +1,70 @@
import UIKit
import CoreData
class EditTagsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var post: Post?
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var tagField: UITextField!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return post!.tags!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tagCell", for: indexPath as IndexPath) as! TagViewCell
cell.label.text = (post?.tags![indexPath.row] as! Tag).name
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if(editingStyle == .delete) {
post?.removeFromTags(post?.tags![indexPath.row] as! Tag)
tableView.reloadData()
}
}
@IBAction func editingFinished(_ sender: Any) {
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let tag = Tag(context: managedContext)
tag.name = tagField.text
post?.addToTags(tag)
tableView.reloadData()
tagField.text?.removeAll()
}
@IBAction func editAction(_ sender: Any) {
tableView.setEditing(true, animated: true)
}
@IBAction func doneAction(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showTag" {
let newViewController = segue.destination as! TagViewController
let index = self.tableView.indexPathForSelectedRow
newViewController.tag = (post?.tags![index!.row] as! Tag).name
}
}
}
extension EditTagsViewController {
static func loadFromStoryboard() -> EditTagsViewController? {
let storyboard = UIStoryboard(name: "Main", bundle: .main)
return storyboard.instantiateViewController(withIdentifier: "EditTagsViewController") as? EditTagsViewController
}
}

View file

@ -0,0 +1,10 @@
<?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>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict>
</plist>

View file

@ -0,0 +1,8 @@
<?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>_XCCurrentVersionName</key>
<string>Gallery.xcdatamodel</string>
</dict>
</plist>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="15400" systemVersion="19A602" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Post" representedClassName="Post" syncable="YES" codeGenerationType="class">
<attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="name" attributeType="String"/>
<attribute name="type" optional="YES" attributeType="String"/>
<relationship name="tags" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="Tag" inverseName="posts" inverseEntity="Tag"/>
</entity>
<entity name="Tag" representedClassName="Tag" syncable="YES" codeGenerationType="class">
<attribute name="name" attributeType="String"/>
<relationship name="posts" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="Post" inverseName="tags" inverseEntity="Post"/>
</entity>
<elements>
<element name="Post" positionX="-63" positionY="-18" width="128" height="103"/>
<element name="Tag" positionX="128.46875" positionY="58.08203125" width="128" height="73"/>
</elements>
</model>

70
Gallery/Info.plist Normal file
View file

@ -0,0 +1,70 @@
<?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>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSUserActivityTypes</key>
<array>
<string>post</string>
</array>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View file

@ -0,0 +1,64 @@
import UIKit
import CoreData
import AVFoundation
class InfoViewController: UIViewController {
var post: Post?
var image: UIImage?
@IBOutlet weak var infoLabel: UILabel!
private func resolutionForLocalVideo(url: URL) -> CGSize? {
guard let track = AVURLAsset(url: url).tracks(withMediaType: AVMediaType.video).first else { return nil }
let size = track.naturalSize.applying(track.preferredTransform)
return CGSize(width: abs(size.width), height: abs(size.height))
}
let documentsPath : URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].absoluteURL
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
var type = "Unavailable"
if(post!.type != nil) {
if(post!.type == "public.image") {
type = "Image"
} else if(post!.type == "public.mpeg-4") {
type = "Video"
}
}
var date = "Unavailable"
if(post!.date != nil) {
date = dateFormatter.string(from: post!.date!)
}
var size = "Unavailable"
if(post?.type == "public.mpeg-4") {
let videoPath = documentsPath.appendingPathComponent(post!.value(forKey: "name") as! String).path
let resolution = resolutionForLocalVideo(url: URL(fileURLWithPath: videoPath))
size = String(format: "%.0f", (resolution?.width)!) + "x" + String(format: "%.0f", (resolution?.height)!)
} else if(image != nil) {
size = String(format: "%.0f", (image?.size.width)!) + "x" + String(format: "%.0f", (image?.size.height)!)
}
infoLabel.text = "Type: " + type + "\nDate: " + date + "\nSize: " + size
}
@IBAction func exitAction(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
}
extension InfoViewController {
static func loadFromStoryboard() -> InfoViewController? {
let storyboard = UIStoryboard(name: "Main", bundle: .main)
return storyboard.instantiateViewController(withIdentifier: "InfoViewController") as? InfoViewController
}
}

View file

@ -0,0 +1,286 @@
import UIKit
import CoreData
import AVFoundation
extension UIActivity.ActivityType {
static let reverseImageSearch =
UIActivity.ActivityType("jorg.Gallery.reverseImage")
}
class ReverseImageSearchService: UIActivity {
var viewController: UIViewController?
var post: Post?
override class var activityCategory: UIActivity.Category {
return .action
}
override var activityType: UIActivity.ActivityType? {
return .reverseImageSearch
}
override var activityTitle: String? {
return NSLocalizedString("Reverse Image Search", comment: "activity title")
}
override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
return true
}
override var activityViewController: UIViewController? {
return ReverseImageViewController.loadFromStoryboard(post: self.post!)
}
}
func generateThumbnail(path: URL) -> UIImage? {
do {
let asset = AVURLAsset(url: path, options: nil)
let imgGenerator = AVAssetImageGenerator(asset: asset)
imgGenerator.appliesPreferredTrackTransform = true
let cgImage = try imgGenerator.copyCGImage(at: CMTimeMake(value: 0, timescale: 1), actualTime: nil)
let thumbnail = UIImage(cgImage: cgImage)
return thumbnail
} catch let error {
print("*** Error generating thumbnail: \(error.localizedDescription)")
return nil
}
}
class PostCollectionView: UICollectionView, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UICollectionViewDropDelegate, UICollectionViewDragDelegate {
var posts: [NSManagedObject] = []
weak var viewController: UIViewController?
private let itemsPerRow: CGFloat = 4
private let sectionInsets = UIEdgeInsets(top: 10.0,
left: 10.0,
bottom: 10.0,
right: 10.0)
let documentsPath : URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].absoluteURL
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return posts.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let post = posts[indexPath.row]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "postCell", for: indexPath as IndexPath) as! PostViewCell
let imagePath = documentsPath.appendingPathComponent(post.value(forKey: "name") as! String).path
if(FileManager.default.fileExists(atPath: imagePath)) {
let type = (post.value(forKey: "type") as? String)
if(type == "public.mpeg-4") {
let imagePath = documentsPath.appendingPathComponent(post.value(forKey: "name") as! String).path
cell.imageView.image = generateThumbnail(path: URL(fileURLWithPath: imagePath))
} else {
cell.imageView.image = UIImage(contentsOfFile: imagePath)
}
} else {
print("could not read " + imagePath)
}
return cell
}
func actualInit(tag: String?) {
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Post")
if(tag != nil) {
let predicate = NSPredicate(format: "ANY tags.name in %@", [tag])
fetchRequest.predicate = predicate
}
do {
posts = try managedContext.fetch(fetchRequest)
DispatchQueue.main.async {
self.reloadData()
}
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
}
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: frame, collectionViewLayout: layout)
self.dragInteractionEnabled = true
self.dataSource = self
self.delegate = self
self.dropDelegate = self
self.dragDelegate = self
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.dragInteractionEnabled = true
self.dataSource = self
self.delegate = self
self.dropDelegate = self
self.dragDelegate = self
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let paddingSpace = sectionInsets.left * (itemsPerRow + 1)
let availableWidth = frame.width - paddingSpace
let widthPerItem = availableWidth / itemsPerRow
return CGSize(width: widthPerItem, height: widthPerItem)
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
insetForSectionAt section: Int) -> UIEdgeInsets {
return sectionInsets
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return sectionInsets.left
}
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: { suggestedActions in
return self.makeContextMenu(post: self.posts[indexPath.row])
})
}
func makeContextMenu(post: NSManagedObject) -> UIMenu {
let newWindow = UIAction(title: "Open in New Window", image: UIImage(systemName: "plus.square.on.square")) { action in
let activity = NSUserActivity(activityType: "post")
activity.userInfo = ["name": post.value(forKey: "name") as! String]
activity.isEligibleForHandoff = true
UIApplication.shared.requestSceneSessionActivation(nil, userActivity: activity, options: nil, errorHandler: nil)
}
let share = UIAction(title: "Share", image: UIImage(systemName: "square.and.arrow.up")) { action in
let index = self.posts.firstIndex(of: post)
let cell = self.cellForItem(at: IndexPath(row: index!, section: 0)) as! PostViewCell
let imageSearch = ReverseImageSearchService()
imageSearch.viewController = self.window?.rootViewController
imageSearch.post = self.posts[index!] as? Post
let activityViewController = UIActivityViewController(activityItems: [cell.imageView.image!], applicationActivities: [imageSearch])
activityViewController.popoverPresentationController?.sourceView = cell.contentView
self.viewController?.present(activityViewController, animated: true, completion:nil)
}
let editTags = UIAction(title: "Tags", image: UIImage(systemName: "tag")) { action in
let viewController = EditTagsViewController.loadFromStoryboard()
viewController!.post = post as? Post
self.viewController?.present(viewController!, animated: true)
}
let info = UIAction(title: "Info", image: UIImage(systemName: "info.circle")) { action in
let index = self.posts.firstIndex(of: post)
let cell = self.cellForItem(at: IndexPath(row: index!, section: 0)) as! PostViewCell
let viewController = InfoViewController.loadFromStoryboard()
viewController!.post = post as? Post
viewController!.image = cell.imageView.image
self.viewController?.present(viewController!, animated: true)
}
let delete = UIAction(title: "Delete", image: UIImage(systemName: "trash"), attributes: .destructive) { action in
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
managedContext.delete(post)
self.posts.removeAll { (obj: NSManagedObject) -> Bool in
return obj == post
}
DispatchQueue.main.async {
self.reloadData()
}
}
#if targetEnvironment(macCatalyst)
return UIMenu(title: "", children: [info, editTags, share, delete])
#else
if UIDevice.current.userInterfaceIdiom == .pad {
return UIMenu(title: "", children: [newWindow, share, info, editTags, delete])
} else {
return UIMenu(title: "", children: [share, info, editTags, delete])
}
#endif
}
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
for item in coordinator.items {
item.dragItem.itemProvider.loadObject(ofClass: UIImage.self,
completionHandler: {(newImage, error) -> Void in
if let image = newImage as? UIImage {
if let data = image.jpegData(compressionQuality: 0.8) {
DispatchQueue.main.async {
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Post", in: managedContext)!
let post = NSManagedObject(entity: entity, insertInto: managedContext)
let uuid = UUID().uuidString
let filename = uuid + ".jpg"
let newPath = self.documentsPath.appendingPathComponent(filename)
try? data.write(to: newPath)
post.setValue(filename, forKeyPath: "name")
try? managedContext.save()
self.posts.append(post)
self.reloadData()
}
}
}
})
}
}
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
let model = posts[indexPath.item]
let itemProvider = NSItemProvider(object: (cellForItem(at: indexPath) as! PostViewCell).imageView.image!)
itemProvider.suggestedName = model.value(forKey: "name") as? String
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = model //We can set the localObject property for convenience
return [dragItem]
}
}

View file

@ -0,0 +1,7 @@
import UIKit
class PostViewCell: UICollectionViewCell {
@IBOutlet weak var imageView: UIImageView!
}

View file

@ -0,0 +1,147 @@
import UIKit
import CoreData
import AVFoundation
import AVKit
class PostViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
var post: NSManagedObject?
var image: UIImage?
var isPopup: Bool = false
@IBOutlet weak var shareButton: UIBarButtonItem!
let documentsPath : URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].absoluteURL
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if(image == nil) {
let imagePath = documentsPath.appendingPathComponent(post!.value(forKey: "name") as! String).path
if((post?.value(forKey: "type") as? String) == "public.mpeg-4") {
self.image = generateThumbnail(path: URL(fileURLWithPath: imagePath))
} else {
self.image = UIImage(contentsOfFile: imagePath)
}
}
imageView.image = self.image
if(isPopup) {
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(closePopup))
}
}
@objc func closePopup() {
UIApplication.shared.requestSceneSessionDestruction((self.view.window?.windowScene!.session)!, options: nil, errorHandler: nil)
}
@IBAction func shareAction(_ sender: Any) {
let imageSearch = ReverseImageSearchService()
imageSearch.viewController = parent
imageSearch.post = self.post as? Post
let activityViewController = UIActivityViewController(activityItems: [self.image!], applicationActivities: [imageSearch])
activityViewController.popoverPresentationController?.barButtonItem = shareButton
self.present(activityViewController, animated: true, completion:nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showTags" {
let newViewController = segue.destination as! EditTagsViewController
newViewController.post = self.post as? Post
} else if segue.identifier == "showInfo" {
let newViewController = segue.destination as! InfoViewController
newViewController.post = self.post as? Post
newViewController.image = self.image
}
}
@IBAction func playAction(_ sender: Any) {
let imagePath = documentsPath.appendingPathComponent(post!.value(forKey: "name") as! String).path
// Create an AVPlayer, passing it the HTTP Live Streaming URL.
let player = AVPlayer(url: URL(fileURLWithPath: imagePath))
// Create a new AVPlayerViewController and pass it a reference to the player.
let controller = AVPlayerViewController()
controller.player = player
// Modally present the player and call the player's play() method when complete.
present(controller, animated: true) {
player.play()
}
}
}
extension PostViewController {
static func loadFromStoryboard() -> PostViewController? {
let storyboard = UIStoryboard(name: "Main", bundle: .main)
return storyboard.instantiateViewController(withIdentifier: "PostViewController") as? PostViewController
}
}
#if targetEnvironment(macCatalyst)
private let EditButtonToolbarIdentifier = NSToolbarItem.Identifier(rawValue: "OurButton")
private let ShareButtonToolbarIdentifier = NSToolbarItem.Identifier(rawValue: "OurButton2")
private let InfoButtonToolbarIdentifier = NSToolbarItem.Identifier(rawValue: "OurButton3")
extension PostViewController: NSToolbarDelegate {
@objc func editTagsAction() {
if(navigationController?.topViewController != self) {
navigationController?.popViewController(animated: true)
} else {
performSegue(withIdentifier: "showTags", sender: nil)
}
}
@objc func infoAction() {
if(navigationController?.topViewController != self) {
navigationController?.popViewController(animated: true)
} else {
performSegue(withIdentifier: "showInfo", sender: nil)
}
}
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [NSToolbarItem.Identifier.flexibleSpace, InfoButtonToolbarIdentifier, EditButtonToolbarIdentifier, ShareButtonToolbarIdentifier]
}
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return toolbarDefaultItemIdentifiers(toolbar)
}
func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
if (itemIdentifier == InfoButtonToolbarIdentifier) {
let barButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.camera,
target: self,
action: #selector(self.infoAction))
let button = NSToolbarItem(itemIdentifier: itemIdentifier, barButtonItem: barButtonItem)
return button
}
if (itemIdentifier == EditButtonToolbarIdentifier) {
let barButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.edit,
target: self,
action: #selector(self.editTagsAction))
let button = NSToolbarItem(itemIdentifier: itemIdentifier, barButtonItem: barButtonItem)
return button
}
if (itemIdentifier == ShareButtonToolbarIdentifier) {
let barButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.action,
target: self,
action: #selector(self.shareAction))
let button = NSToolbarItem(itemIdentifier: itemIdentifier, barButtonItem: barButtonItem)
return button
}
return nil
}
}
#endif

View file

@ -0,0 +1,108 @@
import UIKit
import CoreData
import WebKit
import CoreServices
extension Data {
/// Append string to Data
///
/// Rather than littering my code with calls to `data(using: .utf8)` to convert `String` values to `Data`, this wraps it in a nice convenient little extension to Data. This defaults to converting using UTF-8.
///
/// - parameter string: The string to be added to the `Data`.
mutating func append(_ string: String, using encoding: String.Encoding = .utf8) {
if let data = string.data(using: encoding) {
append(data)
}
}
}
class ReverseImageViewController: UIViewController {
var post: Post?
@IBOutlet weak var webView: WKWebView!
let documentsPath : URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].absoluteURL
private func mimeType(for path: String) -> String {
let url = URL(fileURLWithPath: path)
let pathExtension = url.pathExtension
if let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as NSString, nil)?.takeRetainedValue() {
if let mimetype = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)?.takeRetainedValue() {
return mimetype as String
}
}
return "application/octet-stream"
}
private func createBody(with parameters: [String: String]?, filePathKey: String, paths: [String], boundary: String) throws -> Data {
var body = Data()
if parameters != nil {
for (key, value) in parameters! {
body.append("--\(boundary)\r\n")
body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.append("\(value)\r\n")
}
}
for path in paths {
let url = URL(fileURLWithPath: path)
let filename = url.lastPathComponent
let data = try Data(contentsOf: url)
let mimetype = mimeType(for: path)
body.append("--\(boundary)\r\n")
body.append("Content-Disposition: form-data; name=\"\(filePathKey)\"; filename=\"\(filename)\"\r\n")
body.append("Content-Type: \(mimetype)\r\n\r\n")
body.append(data)
body.append("\r\n")
}
body.append("--\(boundary)--\r\n")
return body
}
private func generateBoundaryString() -> String {
return "Boundary-\(UUID().uuidString)"
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
var components = URLComponents(string: "https://iqdb.org")
components?.queryItems = [URLQueryItem(name: "url", value: "true")]
if let result = components?.url {
let boundary = generateBoundaryString()
var request = URLRequest(url: result)
request.httpMethod = "POST"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
let imagePath = documentsPath.appendingPathComponent(post!.value(forKey: "name") as! String).path
do {
request.httpBody = try createBody(with: nil, filePathKey: "file", paths: [imagePath], boundary: boundary)
} catch let error as NSError {
print(error)
}
webView!.load(request)
}
}
@IBAction func cancelAction(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
}
extension ReverseImageViewController {
static func loadFromStoryboard(post: Post) -> ReverseImageViewController? {
let storyboard = UIStoryboard(name: "Main", bundle: .main)
let viewController = storyboard.instantiateViewController(withIdentifier: "ReverseImageViewController") as? ReverseImageViewController
viewController!.post = post
return viewController
}
}

View file

@ -0,0 +1,85 @@
import UIKit
import CoreData
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let userActivity = connectionOptions.userActivities.first ?? session.stateRestorationActivity {
if !configure(window: window, with: userActivity) {
print("Failed to restore from \(userActivity)")
}
}
#if targetEnvironment(macCatalyst)
guard let windowScene = (scene as? UIWindowScene) else { return }
let toolbar = NSToolbar(identifier: "MyToolbar")
toolbar.delegate = (window?.rootViewController as! UINavigationController).topViewController as? NSToolbarDelegate
windowScene.titlebar!.toolbar = toolbar
windowScene.titlebar!.titleVisibility = .hidden
toolbar.allowsUserCustomization = true
(window?.rootViewController as! UINavigationController).navigationBar.isHidden = true
#endif
}
func sceneDidDisconnect(_ scene: UIScene) {
}
func sceneDidBecomeActive(_ scene: UIScene) {
}
func sceneWillResignActive(_ scene: UIScene) {
}
func sceneWillEnterForeground(_ scene: UIScene) {
}
func sceneDidEnterBackground(_ scene: UIScene) {
(UIApplication.shared.delegate as? AppDelegate)?.saveContext()
}
func configure(window: UIWindow?, with activity: NSUserActivity) -> Bool {
if activity.activityType == "post" {
if let photoID = activity.userInfo?["name"] as? String {
if let photoDetailViewController = PostViewController.loadFromStoryboard() {
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return false
}
let managedContext =
appDelegate.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Post")
request.predicate = NSPredicate(format: "name = %@", photoID)
request.returnsObjectsAsFaults = false
do {
let result = try managedContext.fetch(request)
photoDetailViewController.post = result[0] as? NSManagedObject
photoDetailViewController.isPopup = true
if let navigationController = window?.rootViewController as? UINavigationController {
navigationController.pushViewController(photoDetailViewController, animated: false)
return true
}
} catch _ as NSError {
return false
}
}
}
}
return false
}
}

View file

@ -0,0 +1,50 @@
import UIKit
import CoreData
class SearchViewController: UIViewController {
@IBOutlet weak var collectionView: PostCollectionView!
@IBOutlet weak var searchField: UITextField!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
collectionView.viewController = self
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showPost" {
let newViewController = segue.destination as! PostViewController
let index = self.collectionView.indexPathsForSelectedItems?.first
newViewController.post = self.collectionView.posts[index!.row]
newViewController.image = (self.collectionView.cellForItem(at: index!) as! PostViewCell).imageView.image;
}
}
@IBAction func searchAction(_ sender: Any) {
collectionView.actualInit(tag: searchField.text)
}
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "showPost" {
#if targetEnvironment(macCatalyst)
let index = self.collectionView.indexPathsForSelectedItems?.first
let post = self.collectionView.posts[index!.row]
let activity = NSUserActivity(activityType: "post")
activity.userInfo = ["name": post.value(forKey: "name") as! String]
activity.isEligibleForHandoff = true
UIApplication.shared.requestSceneSessionActivation(nil, userActivity: activity, options: nil, errorHandler: nil)
return false
#else
return true
#endif
}
return super.shouldPerformSegue(withIdentifier: identifier, sender: sender)
}
}

View file

@ -0,0 +1,8 @@
import UIKit
class TagViewCell: UITableViewCell {
@IBOutlet weak var label: UILabel!
}

View file

@ -0,0 +1,25 @@
import UIKit
import CoreData
class TagViewController: UIViewController {
var tag: String?
@IBOutlet weak var collectionView: PostCollectionView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
collectionView.actualInit(tag: self.tag)
collectionView.viewController = self
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showPost" {
let newViewController = segue.destination as! PostViewController
let index = self.collectionView.indexPathsForSelectedItems?.first
newViewController.post = self.collectionView.posts[index!.row]
newViewController.image = (self.collectionView.cellForItem(at: index!) as! PostViewCell).imageView.image;
}
}
}

View file

@ -0,0 +1,150 @@
import UIKit
import CoreData
class ViewController: UIViewController, UIDocumentPickerDelegate {
@IBOutlet weak var collectionView: PostCollectionView!
let documentsPath : URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].absoluteURL
func importFile(path: URL) {
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Post", in: managedContext)!
let post = NSManagedObject(entity: entity, insertInto: managedContext)
let oldPath = path
let newPath = documentsPath.appendingPathComponent(path.lastPathComponent)
do {
try FileManager.default.copyItem(at: oldPath, to: newPath)
if let resourceValues = try? path.resourceValues(forKeys: [.typeIdentifierKey]),
let uti = resourceValues.typeIdentifier {
post.setValue(uti, forKeyPath: "type")
}
post.setValue(Date(), forKeyPath: "date")
post.setValue(path.lastPathComponent, forKeyPath: "name")
try managedContext.save()
collectionView.posts.append(post)
DispatchQueue.main.async {
self.collectionView.reloadData()
}
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
collectionView.actualInit(tag: nil)
collectionView.viewController = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.view.window?.windowScene!.title = "Home"
}
@IBAction func importAction(_ sender: Any) {
let documentPicker = UIDocumentPickerViewController(documentTypes: ["public.image", "public.movie"], in: .import)
documentPicker.delegate = self as UIDocumentPickerDelegate
documentPicker.allowsMultipleSelection = true
present(documentPicker, animated: true)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
for url in urls {
importFile(path: url)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showPost" {
let newViewController = segue.destination as! PostViewController
let index = self.collectionView.indexPathsForSelectedItems?.first
newViewController.post = self.collectionView.posts[index!.row]
newViewController.image = (self.collectionView.cellForItem(at: index!) as! PostViewCell).imageView.image;
}
}
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "showPost" {
#if targetEnvironment(macCatalyst)
let index = self.collectionView.indexPathsForSelectedItems?.first
let post = self.collectionView.posts[index!.row]
let activity = NSUserActivity(activityType: "post")
activity.userInfo = ["name": post.value(forKey: "name") as! String]
activity.isEligibleForHandoff = true
UIApplication.shared.requestSceneSessionActivation(nil, userActivity: activity, options: nil, errorHandler: nil)
return false
#else
return true
#endif
}
return super.shouldPerformSegue(withIdentifier: identifier, sender: sender)
}
}
#if targetEnvironment(macCatalyst)
private let OurButtonToolbarIdentifier = NSToolbarItem.Identifier(rawValue: "OurButton")
private let OurButtonToolbarIdentifier2 = NSToolbarItem.Identifier(rawValue: "OurButton2")
extension ViewController: NSToolbarDelegate {
@objc func searchAction() {
if(navigationController?.topViewController != self) {
navigationController?.popViewController(animated: true)
} else {
performSegue(withIdentifier: "search", sender: nil)
}
}
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [OurButtonToolbarIdentifier2, NSToolbarItem.Identifier.flexibleSpace,
OurButtonToolbarIdentifier]
}
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return toolbarDefaultItemIdentifiers(toolbar)
}
func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
if (itemIdentifier == OurButtonToolbarIdentifier2) {
let barButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.add,
target: self,
action: #selector(self.importAction))
let button = NSToolbarItem(itemIdentifier: itemIdentifier, barButtonItem: barButtonItem)
button.label = "Add";
return button
}
if (itemIdentifier == OurButtonToolbarIdentifier) {
let barButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.search,
target: self,
action: #selector(self.searchAction))
let button = NSToolbarItem(itemIdentifier: itemIdentifier, barButtonItem: barButtonItem)
button.label = "Search";
return button
}
return nil
}
}
#endif

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Joshua Goins
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:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

7
README.md Normal file
View file

@ -0,0 +1,7 @@
# Gallery
A local media gallery. You can import images and videos, and even tag them for searching and filtering.
![showcase image](https://raw.githubusercontent.com/redstrate/gallery/master/misc/showcase.png)
The app is written in Swift, and uses UIKit and CoreData. Thanks to Catalyst, it can run on macOS as well as iOS and iPadOS.

BIN
misc/showcase.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 KiB