First working version with SPM

This commit is contained in:
sivx76 2022-08-09 01:19:46 -07:00
parent 2535832a8a
commit b2e1ba71ec
24 changed files with 2152 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

View File

@ -0,0 +1,403 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 55;
objects = {
/* Begin PBXBuildFile section */
AE5B965428A24F690055CB00 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE5B965328A24F690055CB00 /* AppDelegate.swift */; };
AE5B965628A24F690055CB00 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE5B965528A24F690055CB00 /* SceneDelegate.swift */; };
AE5B965B28A24F690055CB00 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AE5B965928A24F690055CB00 /* Main.storyboard */; };
AE5B965D28A24F6A0055CB00 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AE5B965C28A24F6A0055CB00 /* Assets.xcassets */; };
AE5B966028A24F6A0055CB00 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AE5B965E28A24F6A0055CB00 /* LaunchScreen.storyboard */; };
AE5B967028A24F810055CB00 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE5B966828A24F810055CB00 /* ViewController.swift */; };
AE5B967128A24F810055CB00 /* ThirdViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE5B966928A24F810055CB00 /* ThirdViewController.swift */; };
AE5B967228A24F810055CB00 /* Person.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE5B966A28A24F810055CB00 /* Person.swift */; };
AE5B967328A24F810055CB00 /* SecondViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE5B966B28A24F810055CB00 /* SecondViewController.swift */; };
AE5B967428A24F810055CB00 /* Table.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE5B966D28A24F810055CB00 /* Table.swift */; };
AE5B967528A24F810055CB00 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE5B966E28A24F810055CB00 /* Extensions.swift */; };
AE5B967628A24F810055CB00 /* TableData.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE5B966F28A24F810055CB00 /* TableData.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
AE5B965028A24F690055CB00 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
AE5B965328A24F690055CB00 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
AE5B965528A24F690055CB00 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
AE5B965A28A24F690055CB00 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
AE5B965C28A24F6A0055CB00 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
AE5B965F28A24F6A0055CB00 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
AE5B966128A24F6A0055CB00 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
AE5B966828A24F810055CB00 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
AE5B966928A24F810055CB00 /* ThirdViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThirdViewController.swift; sourceTree = "<group>"; };
AE5B966A28A24F810055CB00 /* Person.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Person.swift; sourceTree = "<group>"; };
AE5B966B28A24F810055CB00 /* SecondViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecondViewController.swift; sourceTree = "<group>"; };
AE5B966D28A24F810055CB00 /* Table.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Table.swift; sourceTree = "<group>"; };
AE5B966E28A24F810055CB00 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
AE5B966F28A24F810055CB00 /* TableData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableData.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
AE5B964D28A24F690055CB00 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
AE5B964728A24F690055CB00 = {
isa = PBXGroup;
children = (
AE5B965228A24F690055CB00 /* Example */,
AE5B965128A24F690055CB00 /* Products */,
);
sourceTree = "<group>";
};
AE5B965128A24F690055CB00 /* Products */ = {
isa = PBXGroup;
children = (
AE5B965028A24F690055CB00 /* Example.app */,
);
name = Products;
sourceTree = "<group>";
};
AE5B965228A24F690055CB00 /* Example */ = {
isa = PBXGroup;
children = (
AE5B966728A24F810055CB00 /* Examples */,
AE5B966C28A24F810055CB00 /* Library */,
AE5B965328A24F690055CB00 /* AppDelegate.swift */,
AE5B965528A24F690055CB00 /* SceneDelegate.swift */,
AE5B965928A24F690055CB00 /* Main.storyboard */,
AE5B965C28A24F6A0055CB00 /* Assets.xcassets */,
AE5B965E28A24F6A0055CB00 /* LaunchScreen.storyboard */,
AE5B966128A24F6A0055CB00 /* Info.plist */,
);
path = Example;
sourceTree = "<group>";
};
AE5B966728A24F810055CB00 /* Examples */ = {
isa = PBXGroup;
children = (
AE5B966828A24F810055CB00 /* ViewController.swift */,
AE5B966928A24F810055CB00 /* ThirdViewController.swift */,
AE5B966A28A24F810055CB00 /* Person.swift */,
AE5B966B28A24F810055CB00 /* SecondViewController.swift */,
);
path = Examples;
sourceTree = "<group>";
};
AE5B966C28A24F810055CB00 /* Library */ = {
isa = PBXGroup;
children = (
AE5B966D28A24F810055CB00 /* Table.swift */,
AE5B966E28A24F810055CB00 /* Extensions.swift */,
AE5B966F28A24F810055CB00 /* TableData.swift */,
);
path = Library;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
AE5B964F28A24F690055CB00 /* Example */ = {
isa = PBXNativeTarget;
buildConfigurationList = AE5B966428A24F6A0055CB00 /* Build configuration list for PBXNativeTarget "Example" */;
buildPhases = (
AE5B964C28A24F690055CB00 /* Sources */,
AE5B964D28A24F690055CB00 /* Frameworks */,
AE5B964E28A24F690055CB00 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = Example;
productName = Example;
productReference = AE5B965028A24F690055CB00 /* Example.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
AE5B964828A24F690055CB00 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1340;
LastUpgradeCheck = 1340;
TargetAttributes = {
AE5B964F28A24F690055CB00 = {
CreatedOnToolsVersion = 13.4.1;
};
};
};
buildConfigurationList = AE5B964B28A24F690055CB00 /* Build configuration list for PBXProject "Example" */;
compatibilityVersion = "Xcode 13.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = AE5B964728A24F690055CB00;
productRefGroup = AE5B965128A24F690055CB00 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
AE5B964F28A24F690055CB00 /* Example */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
AE5B964E28A24F690055CB00 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
AE5B966028A24F6A0055CB00 /* LaunchScreen.storyboard in Resources */,
AE5B965D28A24F6A0055CB00 /* Assets.xcassets in Resources */,
AE5B965B28A24F690055CB00 /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
AE5B964C28A24F690055CB00 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
AE5B967328A24F810055CB00 /* SecondViewController.swift in Sources */,
AE5B967428A24F810055CB00 /* Table.swift in Sources */,
AE5B967228A24F810055CB00 /* Person.swift in Sources */,
AE5B967628A24F810055CB00 /* TableData.swift in Sources */,
AE5B967528A24F810055CB00 /* Extensions.swift in Sources */,
AE5B967028A24F810055CB00 /* ViewController.swift in Sources */,
AE5B965428A24F690055CB00 /* AppDelegate.swift in Sources */,
AE5B967128A24F810055CB00 /* ThirdViewController.swift in Sources */,
AE5B965628A24F690055CB00 /* SceneDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
AE5B965928A24F690055CB00 /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
AE5B965A28A24F690055CB00 /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
AE5B965E28A24F6A0055CB00 /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
AE5B965F28A24F6A0055CB00 /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
AE5B966228A24F6A0055CB00 /* 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++17";
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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
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 = 15.5;
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;
};
AE5B966328A24F6A0055CB00 /* 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++17";
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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
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 = 15.5;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
AE5B966528A24F6A0055CB00 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 9QVBLDBX4H;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Example/Info.plist;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIMainStoryboardFile = Main;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = alemu.ben.Example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
AE5B966628A24F6A0055CB00 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 9QVBLDBX4H;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Example/Info.plist;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIMainStoryboardFile = Main;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = alemu.ben.Example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
AE5B964B28A24F690055CB00 /* Build configuration list for PBXProject "Example" */ = {
isa = XCConfigurationList;
buildConfigurations = (
AE5B966228A24F6A0055CB00 /* Debug */,
AE5B966328A24F6A0055CB00 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
AE5B966428A24F6A0055CB00 /* Build configuration list for PBXNativeTarget "Example" */ = {
isa = XCConfigurationList;
buildConfigurations = (
AE5B966528A24F6A0055CB00 /* Debug */,
AE5B966628A24F6A0055CB00 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = AE5B964828A24F690055CB00 /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</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>

View File

@ -0,0 +1,36 @@
//
// AppDelegate.swift
// Example
//
// Created by Ben Alemu on 8/9/22.
//
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}

View File

@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

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

View File

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

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,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="Example" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="20" y="84"/>
</scene>
</scenes>
<resources>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>

View File

@ -0,0 +1,29 @@
//
// Person.swift
// Table
//
// Created by Ben Alemu on 8/8/22.
//
import UIKit
struct Person {
let name: String
let age: Int
let imageSource: String
}
extension Person: TableData {
var title: String {
return name
}
var subtitle: String? {
return "\(age)"
}
var image: UIImage? {
return UIImage(systemName: imageSource) ?? nil
}
}

View File

@ -0,0 +1,60 @@
//
// SecondViewController.swift
// Table
//
// Created by Ben Alemu on 8/8/22.
//
import UIKit
class SecondViewController: UIViewController {
var table = Table<Person>()
override func viewDidLoad() {
super.viewDidLoad()
// Build data
let personOne = Person(name: "Paul", age: 24, imageSource: "person.circle.fill")
let personTwo = Person(name: "Zhao", age: 31, imageSource: "shareplay")
let personThree = Person(name: "Maria", age: 28, imageSource: "eye.fill")
let personFour = Person(name: "Vincent", age: 43, imageSource: "brain")
// Form table
self.table = Table(reuseIdentifier: "id",
data: [personOne, personTwo, personThree, personFour] ,
frame: view.frame)
{ indexPath, element, cell in
print("Tapped on element: \(element)")
cell.addCheckmark()
}
// Show TableView
let tableView = table.show()
view.addSubview(tableView)
// Customize appearance
table.setHeader(title: "Hello world!")
table.setCellHeight(height: 50)
table.setAccessory(style: .disclosureIndicator)
table.setTitleFont(font: .boldSystemFont(ofSize: 18))
table.setSubtitleFont(font: .italicSystemFont(ofSize: 14))
table.setSubtitleColor(color: .brown)
table.setImageTint(color: .red)
}
}

View File

@ -0,0 +1,35 @@
//
// SecondViewController.swift
// Table
//
// Created by Ben Alemu on 8/8/22.
//
import UIKit
class ThirdViewController: UIViewController {
var table = Table<String>(datasets: [
["San Diego", "Hawaii", "Paris", "Buenos Aires"],
["Swim", "Run", "Eat", "Play"],
["Drive", "Fly"]
])
override func viewDidLoad() {
super.viewDidLoad()
// Show TableView
let tableView = table.show()
view.addSubview(tableView)
// Customize appearance
table.setHeader(titles: ["Locations", "Activities", "Travel"])
}
}

View File

@ -0,0 +1,31 @@
//
// ViewController.swift
// Table
//
// Created by Ben Alemu on 8/6/22.
//
import UIKit
class ViewController: UIViewController {
var table = Table(data: ["Krishna", "Thomas", "Jodeci", "Susan"])
override func viewDidLoad() {
super.viewDidLoad()
// Show TableView
let tableView = table.show()
view.addSubview(tableView)
// Customization
table.setHeader(title: "Family")
table.setAccessory(style: .disclosureIndicator)
}
}

View File

@ -0,0 +1,25 @@
<?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>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<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>
</dict>
</plist>

View File

@ -0,0 +1,86 @@
//
// Extensions.swift
// Table
//
// Created by Ben Alemu on 8/6/22.
//
import UIKit
// MARK: - Conforming to TableData
extension String: TableData {
var title: String {
get {
self
}
}
var subtitle: String? {
get {
nil
}
}
var image: UIImage? {
get {
nil
}
}
}
extension Int: TableData {
var title: String {
get {
"\(self)"
}
}
var subtitle: String? {
get {
nil
}
}
var image: UIImage? {
get {
nil
}
}
}
// MARK: - Utilities
extension UITableViewCell {
func addCheckmark() {
let cell = self
if cell.accessoryType == .checkmark {
cell.accessoryType = .none
} else {
cell.accessoryType = .checkmark
}
}
}
extension UIViewController {
func showAlert(title: String, message: String?) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil ))
present(alert, animated: true)
}
}
// To prevent Index out of Error issues with working with multiple Sections and fetching Table Headers
extension Collection where Indices.Iterator.Element == Index {
subscript (safe index: Index) -> Iterator.Element? {
return indices.contains(index) ? self[index] : nil
}
}

View File

@ -0,0 +1,490 @@
//
// Table.swift
// Table
//
// Created by Ben Alemu on 8/6/22.
//
import UIKit
// MARK: - Documentation Compiler (DocC)
/// A UITableView that uses Generics to display data that conforms to the ``TableData`` protocol.
///
/// Build and display TableView in 3 lines of code.
///
/// No need to conform to UITableViewDelegate and UITableViewDataSource protocols.
///
/// Conform your custom Struct or Class to the TableData protocol.
///
///
/// ```swift
/// var table = Table(data: ["Krishna", "Thomas", "Jodeci", "Susan"])
///
///
/// override func viewDidLoad() {
/// super.viewDidLoad()
///
/// // Show TableView
/// let tableView = table.show()
/// view.addSubview(tableView)
///
///
/// // Customization
/// table.setHeader(title: "Family")
/// table.setAccessory(style: .disclosureIndicator)
/// }
/// ```
///
/// ```swift
/// let table = Table(reuseIdentifier: "id",
/// data: ["Krishna", "Thomas", "Jodeci", "Susan"],
/// frame: view.frame)
/// {indexPath, element, cell in
/// print("Tapped on element: \(element)")
/// cell.addCheckmark()
/// }
///
/// let tableView = table.show()
///
///
/// // Customization
/// table.setHeader(title: "Family!")
/// table.setAccessory(style: .disclosureIndicator)
///
///
/// view.addSubview(tableView)
/// ```
class Table<D: TableData>: NSObject, UITableViewDelegate, UITableViewDataSource {
// MARK: - UI Elements
private var tableView = UITableView()
// MARK: - Variables
/// The data used to show cells for the UITableView.
///
/// Each element produces one UITableViewCell.
var dataSource = [D]() {
didSet {
tableView.reloadData()
}
}
/// This multidimensional array produces one TableView Section, for each Array provided.
var datasets = [[D]]() {
didSet {
tableView.reloadData()
}
}
private var numberOfSections: Int = 1 {
didSet {
tableView.reloadData()
}
}
// Initialized properties
private var identifier = String()
private var frame = CGRect()
private var cellTappedAction: (IndexPath, D, UITableViewCell) -> Void
// Customize appearance
private var title: String? = nil {
didSet {
tableView.reloadData()
}
}
private var titles: [String]? = nil {
didSet {
tableView.reloadData()
}
}
private var titleFont: UIFont = UIFont.systemFont(ofSize: 16) {
didSet {
tableView.reloadData()
}
}
private var titleColor: UIColor = .black {
didSet {
tableView.reloadData()
}
}
private var subtitleFont: UIFont = UIFont.systemFont(ofSize: 12) {
didSet {
tableView.reloadData()
}
}
private var subtitleColor: UIColor = .black {
didSet {
tableView.reloadData()
}
}
private var imageTint: UIColor = .systemBlue {
didSet {
tableView.reloadData()
}
}
private var cellHeight: Float = 44.0 {
didSet {
tableView.reloadData()
}
}
private var accessory: UITableViewCell.AccessoryType = .none {
didSet {
tableView.reloadData()
}
}
// MARK: - Initializers
/// Builds a placeholder TableView with no initial data.
convenience override init() {
let data: [D] = []
self.init(data: data)
}
/// Builds a TableView with a specific hard-coded Reuse identifier and a specific Frame.
convenience init(data: [D]) {
let generatedIdentifier: String = "ABCD"
self.init(reuseIdentifier: generatedIdentifier, data: data)
}
/// Builds a TableView with a specific hard-coded Reuse identifier and a specific Frame.
convenience init(datasets: [[D]]) {
let generatedIdentifier: String = "ABCD"
self.init(reuseIdentifier: generatedIdentifier, datasets: datasets)
}
/// Builds a TableView with a frame of width 400 pixels and height of 800 pixels.
convenience init(reuseIdentifier: String, data: [D]) {
let emptyFrame: CGRect = CGRect(x: 0, y: 20, width: 400, height: 800)
self.init(reuseIdentifier: reuseIdentifier, data: data, frame: emptyFrame)
}
/// Builds a TableView with a specific hard-coded Reuse identifier and a specific Frame.
convenience init(reuseIdentifier: String, datasets: [[D]]) {
let emptyFrame: CGRect = CGRect(x: 0, y: 20, width: 400, height: 800)
self.init(reuseIdentifier: reuseIdentifier, datasets: datasets, frame: emptyFrame)
}
/// Builds a TableView with a specific cellTappedAction() called when each cell is tapped.*
required init(reuseIdentifier: String, data: [D], frame: CGRect) {
self.cellTappedAction = { (index, element, cell) -> Void in
print("Tapped on element: \(element)")
}
super.init()
self.identifier = reuseIdentifier
self.dataSource = data
self.frame = frame
registerCell()
configureDelegates()
tableView.frame = frame
}
/// Builds a TableView with a specific cellTappedAction() called when each cell is tapped.*
required init(reuseIdentifier: String, datasets: [[D]], frame: CGRect) {
self.cellTappedAction = { (index, element, cell) -> Void in
print("Tapped on element: \(element)")
}
super.init()
self.identifier = reuseIdentifier
self.datasets = datasets
self.frame = frame
registerCell()
configureDelegates()
tableView.frame = frame
self.numberOfSections = datasets.count
}
/// Builds a TableView with the provided closure called when we tap on each cell.
convenience init(reuseIdentifier: String, data: [D], frame: CGRect, action: @escaping (IndexPath, D, UITableViewCell) -> Void) {
self.init(reuseIdentifier: reuseIdentifier, data: data, frame: frame)
self.cellTappedAction = action
}
/// Builds a TableView with the provided closure called when we tap on each cell.
convenience init(reuseIdentifier: String, datasets: [[D]], frame: CGRect, action: @escaping (IndexPath, D, UITableViewCell) -> Void) {
self.init(reuseIdentifier: reuseIdentifier, datasets: datasets, frame: frame)
self.cellTappedAction = action
}
// MARK: - Helper methods
private func registerCell() {
tableView.register(UITableViewCell.self, forCellReuseIdentifier: identifier)
}
private func configureDelegates() {
tableView.delegate = self
tableView.dataSource = self
}
private func determineElement(for indexPath: IndexPath) -> D? {
var element: D? = nil
if numberOfSections > 1 {
let section = indexPath.section
let array: [D] = datasets[section]
element = array[indexPath.row]
} else {
element = dataSource[indexPath.row]
}
return element
}
// MARK: - Public methods - Data
/// Add an Element to the end of our ``dataSource``.
public func append(element: D) {
self.dataSource.append(element)
}
/// Replace our ``dataSource`` with a new Array.
public func setData(data: [D]) {
self.dataSource = data
self.numberOfSections = 1
}
/// Replace our ``datasets`` with a new multidimensional Array.
public func setDatasets(datasets: [[D]]) {
self.datasets = datasets
self.numberOfSections = datasets.count
}
/// Randomize all the elements in our ``dataSource``.
public func shuffleElements() {
self.dataSource.shuffle()
self.datasets.shuffle()
}
/// Removes all elements from the ``dataSource``.
public func removeAllElements() {
self.dataSource = []
self.datasets = []
}
// MARK: - Public methods - Appearance
/// Change the font of titleLabel.
public func setTitleFont(font: UIFont) {
self.titleFont = font
}
/// Change the color of the titleLabel.
public func setTitleColor(color: UIColor) {
self.titleColor = color
}
/// Change the font of the detailTextLabel.
public func setSubtitleFont(font: UIFont) {
self.subtitleFont = font
}
/// Change the color of the detailTextLabel.
public func setSubtitleColor(color: UIColor) {
self.subtitleColor = color
}
/// Change the color of system-defined Images in our imageView.
public func setImageTint(color: UIColor) {
self.imageTint = color
}
/// Change the Title shown on the top of the TableView.
public func setHeader(title: String) {
self.title = title
}
/// Provide the Titles that will be shown for each Section of the TableView.
public func setHeader(titles: [String]) {
self.titles = titles
}
/// Add a View pinned to the top of the TableView
public func setHeaderView(view: UIView) {
self.tableView.tableHeaderView = view
}
/// Add a View pinned to the bottom of the TableView
public func setFooterView(view: UIView) {
self.tableView.tableFooterView = view
}
/// Change the default accessory shown for each cell.
public func setAccessory(style: UITableViewCell.AccessoryType) {
self.accessory = style
}
/// Change the height for each row.
public func setCellHeight(height: Float) {
self.cellHeight = height
}
/// Change the closure called when each cell is tapped.
public func setAction(action: @escaping (IndexPath, D, UITableViewCell) -> Void) {
self.cellTappedAction = action
}
// MARK: - Public methods - Access View
/// Outputs the TableView generated by the ``Table`` class.
///
/// - Returns: The UITableView created by the Table.
func show() -> UITableView {
tableView.isHidden = false
return tableView
}
/// Hide the TableView from the View.
public func hide() {
tableView.isHidden = true
}
/// Change the Frame or dimensions of the TableView.
///
/// - Parameter frame: Changes the x and y-position, width and height of the TableView.
public func setFrame(frame: CGRect) {
self.frame = frame
tableView.frame = frame
}
/// Outputs the frame of the TableView.
///
/// - Returns: A rectangle showing the x and y-position, width and height of the TableView.
public func showFrame() -> CGRect {
return frame
}
// MARK: - Tableview methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if numberOfSections > 1 {
guard let elements = datasets[safe: section] else { return 0 }
return elements.count
} else {
return dataSource.count
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return numberOfSections
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if numberOfSections > 1 {
guard let title = titles?[safe: section] else { return "Other" }
return title
} else {
return title
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: identifier)
// Determine element
guard let element: D = determineElement(for: indexPath) else { return UITableViewCell() }
// Customize appearance
cell.textLabel?.text = element.title
cell.textLabel?.font = titleFont
cell.textLabel?.textColor = titleColor
cell.detailTextLabel?.text = element.subtitle ?? nil
cell.detailTextLabel?.font = subtitleFont
cell.detailTextLabel?.textColor = subtitleColor
cell.imageView?.image = element.image ?? nil
cell.imageView?.tintColor = imageTint
cell.accessoryType = accessory
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
// Determine element
guard let element: D = determineElement(for: indexPath) else { return }
let indexPath: IndexPath = indexPath
guard let cell = tableView.cellForRow(at: indexPath) else { return }
cellTappedAction(indexPath, element, cell)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return CGFloat(cellHeight)
}
}

View File

@ -0,0 +1,17 @@
//
// TableData.swift
// Table
//
// Created by Ben Alemu on 8/6/22.
//
import UIKit
/// The protocol used to make a Struct or Class be inserted into the ``Example_Project/Table/dataSource`` of ``Example_Project/Table``.
protocol TableData {
var title: String { get }
var subtitle: String? { get }
var image: UIImage? { get }
}

View File

@ -0,0 +1,52 @@
//
// SceneDelegate.swift
// Example
//
// Created by Ben Alemu on 8/9/22.
//
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard let _ = (scene as? UIWindowScene) else { return }
}
func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
func sceneWillResignActive(_ scene: UIScene) {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
}

25
Package.swift Normal file
View File

@ -0,0 +1,25 @@
// swift-tools-version: 5.6
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "Swift Table",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "Swift Table",
targets: ["Swift Table"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "Swift Table",
dependencies: []),
]
)

79
README.md Normal file
View File

@ -0,0 +1,79 @@
# Swift Table
The fastest and easiest way to present a UITableView - **in 3 lines of code**.
Powered by Generics to present any data type.
No need to use the UITableViewDelegate and UITableViewDataSource protocols.
Swift Table is a free and open-source library that removes the hundreds of lines of code you need to create a fully-customized TableView.
![](First%20screenshot.png)
![](Second%20screenshot.png)
## Example:
``` swift
var table = Table(data: [“Home”, “Videos”, “Articles”, “Settings”])
let table = table.show()
view.addSubview(tableView)
```
That is all you need!
## Another example:
``` swift
let table = Table(reuseIdentifier: “id”,
data: [“Krishna”, “Thomas”, “Jaimie”, “Susan”],
frame: view.frame)
{indexPath, element, cell in
print(“Tapped on element: \(element)”)
cell.addCheckmark()
}
let tableView = table.show()
// Customization
table.setHeader(title: “Family!”)
table.setAccessory(style: .disclosureIndicator)
```
## Features:
* Supports single and multiple sections of data
* Can show a subtitle and image label for each cell
* Provide your own action triggered when a cell is tapped
* Over 20 public methods to customize most aspects of TableViews - headers, cellHeight, accessories, fonts, colors and much more
* Includes support for Documentation Compiler (DocC) to show code documentation and tips as you type
## Benefits:
* Reduce the hundreds of lines of codes you use for TableViews
* Save dozens of hours of development time
* Prevent confusing errors
## Quick tips:
* If you want to show a TableView with one section, insert one array into the **data** parameter or call the .setData() method. If you want to show a TableView with multiple sections, insert a multidimensional array into the **datasets** parameter or call the .setDatasets() method.
* To perform any changes, try using the public methods on the Table class. For example: `table.setCellHeight(54.0)` or `table.setTitleColor(color: blue)`
* To present your own custom class or struct, conform to the ::TableData:: protocol.
## Created by:
Benyam Alemu Sood and Jigyasaa Alemu Sood, 2022.
Swift Table is a free and opensource library distributed under the **MIT License**. You may use the source code for free in any of our personal and commercial libraries.
If you would like to, you may create any articles, tutorials or videos describing any component of this library.
Swift Table will always be free to use and openly available.
## Collaboration:
We are in active development. We welcome collaboration.
Feel free to send any pull requests or proposed changes to our codebase. Submit your ideas and code improvements.

View File

@ -0,0 +1,86 @@
//
// Extensions.swift
// Table
//
// Created by Ben Alemu on 8/6/22.
//
import UIKit
// MARK: - Conforming to TableData
extension String: TableData {
var title: String {
get {
self
}
}
var subtitle: String? {
get {
nil
}
}
var image: UIImage? {
get {
nil
}
}
}
extension Int: TableData {
var title: String {
get {
"\(self)"
}
}
var subtitle: String? {
get {
nil
}
}
var image: UIImage? {
get {
nil
}
}
}
// MARK: - Utilities
extension UITableViewCell {
func addCheckmark() {
let cell = self
if cell.accessoryType == .checkmark {
cell.accessoryType = .none
} else {
cell.accessoryType = .checkmark
}
}
}
extension UIViewController {
func showAlert(title: String, message: String?) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil ))
present(alert, animated: true)
}
}
// To prevent Index out of Error issues with working with multiple Sections and fetching Table Headers
extension Collection where Indices.Iterator.Element == Index {
subscript (safe index: Index) -> Iterator.Element? {
return indices.contains(index) ? self[index] : nil
}
}

View File

@ -0,0 +1,490 @@
//
// Table.swift
// Table
//
// Created by Ben Alemu on 8/6/22.
//
import UIKit
// MARK: - Documentation Compiler (DocC)
/// A UITableView that uses Generics to display data that conforms to the ``TableData`` protocol.
///
/// Build and display TableView in 3 lines of code.
///
/// No need to conform to UITableViewDelegate and UITableViewDataSource protocols.
///
/// Conform your custom Struct or Class to the TableData protocol.
///
///
/// ```swift
/// var table = Table(data: ["Krishna", "Thomas", "Jodeci", "Susan"])
///
///
/// override func viewDidLoad() {
/// super.viewDidLoad()
///
/// // Show TableView
/// let tableView = table.show()
/// view.addSubview(tableView)
///
///
/// // Customization
/// table.setHeader(title: "Family")
/// table.setAccessory(style: .disclosureIndicator)
/// }
/// ```
///
/// ```swift
/// let table = Table(reuseIdentifier: "id",
/// data: ["Krishna", "Thomas", "Jodeci", "Susan"],
/// frame: view.frame)
/// {indexPath, element, cell in
/// print("Tapped on element: \(element)")
/// cell.addCheckmark()
/// }
///
/// let tableView = table.show()
///
///
/// // Customization
/// table.setHeader(title: "Family!")
/// table.setAccessory(style: .disclosureIndicator)
///
///
/// view.addSubview(tableView)
/// ```
class Table<D: TableData>: NSObject, UITableViewDelegate, UITableViewDataSource {
// MARK: - UI Elements
private var tableView = UITableView()
// MARK: - Variables
/// The data used to show cells for the UITableView.
///
/// Each element produces one UITableViewCell.
var dataSource = [D]() {
didSet {
tableView.reloadData()
}
}
/// This multidimensional array produces one TableView Section, for each Array provided.
var datasets = [[D]]() {
didSet {
tableView.reloadData()
}
}
private var numberOfSections: Int = 1 {
didSet {
tableView.reloadData()
}
}
// Initialized properties
private var identifier = String()
private var frame = CGRect()
private var cellTappedAction: (IndexPath, D, UITableViewCell) -> Void
// Customize appearance
private var title: String? = nil {
didSet {
tableView.reloadData()
}
}
private var titles: [String]? = nil {
didSet {
tableView.reloadData()
}
}
private var titleFont: UIFont = UIFont.systemFont(ofSize: 16) {
didSet {
tableView.reloadData()
}
}
private var titleColor: UIColor = .black {
didSet {
tableView.reloadData()
}
}
private var subtitleFont: UIFont = UIFont.systemFont(ofSize: 12) {
didSet {
tableView.reloadData()
}
}
private var subtitleColor: UIColor = .black {
didSet {
tableView.reloadData()
}
}
private var imageTint: UIColor = .systemBlue {
didSet {
tableView.reloadData()
}
}
private var cellHeight: Float = 44.0 {
didSet {
tableView.reloadData()
}
}
private var accessory: UITableViewCell.AccessoryType = .none {
didSet {
tableView.reloadData()
}
}
// MARK: - Initializers
/// Builds a placeholder TableView with no initial data.
convenience override init() {
let data: [D] = []
self.init(data: data)
}
/// Builds a TableView with a specific hard-coded Reuse identifier and a specific Frame.
convenience init(data: [D]) {
let generatedIdentifier: String = "ABCD"
self.init(reuseIdentifier: generatedIdentifier, data: data)
}
/// Builds a TableView with a specific hard-coded Reuse identifier and a specific Frame.
convenience init(datasets: [[D]]) {
let generatedIdentifier: String = "ABCD"
self.init(reuseIdentifier: generatedIdentifier, datasets: datasets)
}
/// Builds a TableView with a frame of width 400 pixels and height of 800 pixels.
convenience init(reuseIdentifier: String, data: [D]) {
let emptyFrame: CGRect = CGRect(x: 0, y: 20, width: 400, height: 800)
self.init(reuseIdentifier: reuseIdentifier, data: data, frame: emptyFrame)
}
/// Builds a TableView with a specific hard-coded Reuse identifier and a specific Frame.
convenience init(reuseIdentifier: String, datasets: [[D]]) {
let emptyFrame: CGRect = CGRect(x: 0, y: 20, width: 400, height: 800)
self.init(reuseIdentifier: reuseIdentifier, datasets: datasets, frame: emptyFrame)
}
/// Builds a TableView with a specific cellTappedAction() called when each cell is tapped.*
required init(reuseIdentifier: String, data: [D], frame: CGRect) {
self.cellTappedAction = { (index, element, cell) -> Void in
print("Tapped on element: \(element)")
}
super.init()
self.identifier = reuseIdentifier
self.dataSource = data
self.frame = frame
registerCell()
configureDelegates()
tableView.frame = frame
}
/// Builds a TableView with a specific cellTappedAction() called when each cell is tapped.*
required init(reuseIdentifier: String, datasets: [[D]], frame: CGRect) {
self.cellTappedAction = { (index, element, cell) -> Void in
print("Tapped on element: \(element)")
}
super.init()
self.identifier = reuseIdentifier
self.datasets = datasets
self.frame = frame
registerCell()
configureDelegates()
tableView.frame = frame
self.numberOfSections = datasets.count
}
/// Builds a TableView with the provided closure called when we tap on each cell.
convenience init(reuseIdentifier: String, data: [D], frame: CGRect, action: @escaping (IndexPath, D, UITableViewCell) -> Void) {
self.init(reuseIdentifier: reuseIdentifier, data: data, frame: frame)
self.cellTappedAction = action
}
/// Builds a TableView with the provided closure called when we tap on each cell.
convenience init(reuseIdentifier: String, datasets: [[D]], frame: CGRect, action: @escaping (IndexPath, D, UITableViewCell) -> Void) {
self.init(reuseIdentifier: reuseIdentifier, datasets: datasets, frame: frame)
self.cellTappedAction = action
}
// MARK: - Helper methods
private func registerCell() {
tableView.register(UITableViewCell.self, forCellReuseIdentifier: identifier)
}
private func configureDelegates() {
tableView.delegate = self
tableView.dataSource = self
}
private func determineElement(for indexPath: IndexPath) -> D? {
var element: D? = nil
if numberOfSections > 1 {
let section = indexPath.section
let array: [D] = datasets[section]
element = array[indexPath.row]
} else {
element = dataSource[indexPath.row]
}
return element
}
// MARK: - Public methods - Data
/// Add an Element to the end of our ``dataSource``.
public func append(element: D) {
self.dataSource.append(element)
}
/// Replace our ``dataSource`` with a new Array.
public func setData(data: [D]) {
self.dataSource = data
self.numberOfSections = 1
}
/// Replace our ``datasets`` with a new multidimensional Array.
public func setDatasets(datasets: [[D]]) {
self.datasets = datasets
self.numberOfSections = datasets.count
}
/// Randomize all the elements in our ``dataSource``.
public func shuffleElements() {
self.dataSource.shuffle()
self.datasets.shuffle()
}
/// Removes all elements from the ``dataSource``.
public func removeAllElements() {
self.dataSource = []
self.datasets = []
}
// MARK: - Public methods - Appearance
/// Change the font of titleLabel.
public func setTitleFont(font: UIFont) {
self.titleFont = font
}
/// Change the color of the titleLabel.
public func setTitleColor(color: UIColor) {
self.titleColor = color
}
/// Change the font of the detailTextLabel.
public func setSubtitleFont(font: UIFont) {
self.subtitleFont = font
}
/// Change the color of the detailTextLabel.
public func setSubtitleColor(color: UIColor) {
self.subtitleColor = color
}
/// Change the color of system-defined Images in our imageView.
public func setImageTint(color: UIColor) {
self.imageTint = color
}
/// Change the Title shown on the top of the TableView.
public func setHeader(title: String) {
self.title = title
}
/// Provide the Titles that will be shown for each Section of the TableView.
public func setHeader(titles: [String]) {
self.titles = titles
}
/// Add a View pinned to the top of the TableView
public func setHeaderView(view: UIView) {
self.tableView.tableHeaderView = view
}
/// Add a View pinned to the bottom of the TableView
public func setFooterView(view: UIView) {
self.tableView.tableFooterView = view
}
/// Change the default accessory shown for each cell.
public func setAccessory(style: UITableViewCell.AccessoryType) {
self.accessory = style
}
/// Change the height for each row.
public func setCellHeight(height: Float) {
self.cellHeight = height
}
/// Change the closure called when each cell is tapped.
public func setAction(action: @escaping (IndexPath, D, UITableViewCell) -> Void) {
self.cellTappedAction = action
}
// MARK: - Public methods - Access View
/// Outputs the TableView generated by the ``Table`` class.
///
/// - Returns: The UITableView created by the Table.
func show() -> UITableView {
tableView.isHidden = false
return tableView
}
/// Hide the TableView from the View.
public func hide() {
tableView.isHidden = true
}
/// Change the Frame or dimensions of the TableView.
///
/// - Parameter frame: Changes the x and y-position, width and height of the TableView.
public func setFrame(frame: CGRect) {
self.frame = frame
tableView.frame = frame
}
/// Outputs the frame of the TableView.
///
/// - Returns: A rectangle showing the x and y-position, width and height of the TableView.
public func showFrame() -> CGRect {
return frame
}
// MARK: - Tableview methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if numberOfSections > 1 {
guard let elements = datasets[safe: section] else { return 0 }
return elements.count
} else {
return dataSource.count
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return numberOfSections
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if numberOfSections > 1 {
guard let title = titles?[safe: section] else { return "Other" }
return title
} else {
return title
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: identifier)
// Determine element
guard let element: D = determineElement(for: indexPath) else { return UITableViewCell() }
// Customize appearance
cell.textLabel?.text = element.title
cell.textLabel?.font = titleFont
cell.textLabel?.textColor = titleColor
cell.detailTextLabel?.text = element.subtitle ?? nil
cell.detailTextLabel?.font = subtitleFont
cell.detailTextLabel?.textColor = subtitleColor
cell.imageView?.image = element.image ?? nil
cell.imageView?.tintColor = imageTint
cell.accessoryType = accessory
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
// Determine element
guard let element: D = determineElement(for: indexPath) else { return }
let indexPath: IndexPath = indexPath
guard let cell = tableView.cellForRow(at: indexPath) else { return }
cellTappedAction(indexPath, element, cell)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return CGFloat(cellHeight)
}
}

View File

@ -0,0 +1,17 @@
//
// TableData.swift
// Table
//
// Created by Ben Alemu on 8/6/22.
//
import UIKit
/// The protocol used to make a Struct or Class be inserted into the ``Table/Table/dataSource`` of ``Table/Table``.
protocol TableData {
var title: String { get }
var subtitle: String? { get }
var image: UIImage? { get }
}