First working version with SPM
This commit is contained in:
parent
2535832a8a
commit
b2e1ba71ec
|
@ -0,0 +1,9 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
|
@ -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 */;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
|
@ -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>
|
|
@ -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.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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"])
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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>
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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 }
|
||||
}
|
||||
|
|
@ -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.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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: []),
|
||||
]
|
||||
)
|
|
@ -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.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
## 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.
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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 }
|
||||
}
|
||||
|
Loading…
Reference in New Issue