diff --git a/.gitignore b/.gitignore index 3f994ac..fe20185 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ Thumbs.db SecretKey.json Keys.json +Pods/ #### # Xcode 4 - semi-personal settings diff --git a/OpenWeatherApp.xcodeproj/project.pbxproj b/OpenWeatherApp.xcodeproj/project.pbxproj index 5391e5f..fc39c4f 100644 --- a/OpenWeatherApp.xcodeproj/project.pbxproj +++ b/OpenWeatherApp.xcodeproj/project.pbxproj @@ -3,15 +3,19 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 51; objects = { /* Begin PBXBuildFile section */ + AB2F4E0D9663AF69B2166079 /* Pods_OpenWeatherApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0A948C8BFEDA0218ADB76376 /* Pods_OpenWeatherApp.framework */; }; FF517CD82623629600F1B1EF /* Readme.md in Resources */ = {isa = PBXBuildFile; fileRef = FF517CD72623629600F1B1EF /* Readme.md */; }; FF61FD5F2622AEF000063A50 /* SearchCityNameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF61FD5E2622AEF000063A50 /* SearchCityNameViewController.swift */; }; + FF6AB719265E696B002E189F /* InternetConnectionCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6AB718265E696B002E189F /* InternetConnectionCheck.swift */; }; + FF6DF6CF265B7BE100DCBD6D /* RealmClasses.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6DF6CE265B7BE100DCBD6D /* RealmClasses.swift */; }; FF7751312626A0CD00740D62 /* CodableStructures.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7751302626A0CD00740D62 /* CodableStructures.swift */; }; FF7751342626A21400740D62 /* AutoCompleteLocationCustomTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7751332626A21400740D62 /* AutoCompleteLocationCustomTableViewCell.swift */; }; FF885A6426149EAB00F0FAFF /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF885A6326149EAB00F0FAFF /* Extensions.swift */; }; + FF89AEB426636BC700BCC168 /* RealmDataAccessUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF89AEB326636BC700BCC168 /* RealmDataAccessUtility.swift */; }; FFB1CC83262B4CC600A692FE /* FileReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB1CC82262B4CC600A692FE /* FileReader.swift */; }; FFBDC71F2615CBE5002779BE /* CustomCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBDC71E2615CBE5002779BE /* CustomCollectionViewCell.swift */; }; FFBF0E272612F0370028C11B /* Keys.json in Resources */ = {isa = PBXBuildFile; fileRef = FFBF0E262612F0370028C11B /* Keys.json */; }; @@ -26,11 +30,17 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 0A948C8BFEDA0218ADB76376 /* Pods_OpenWeatherApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_OpenWeatherApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 0EFBF87E588EBAF0647186BD /* Pods-OpenWeatherApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OpenWeatherApp.debug.xcconfig"; path = "Target Support Files/Pods-OpenWeatherApp/Pods-OpenWeatherApp.debug.xcconfig"; sourceTree = ""; }; + 8CB77FEDD2E7F4F98973E887 /* Pods-OpenWeatherApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OpenWeatherApp.release.xcconfig"; path = "Target Support Files/Pods-OpenWeatherApp/Pods-OpenWeatherApp.release.xcconfig"; sourceTree = ""; }; FF517CD72623629600F1B1EF /* Readme.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Readme.md; sourceTree = ""; }; FF61FD5E2622AEF000063A50 /* SearchCityNameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchCityNameViewController.swift; sourceTree = ""; }; + FF6AB718265E696B002E189F /* InternetConnectionCheck.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternetConnectionCheck.swift; sourceTree = ""; }; + FF6DF6CE265B7BE100DCBD6D /* RealmClasses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealmClasses.swift; sourceTree = ""; }; FF7751302626A0CD00740D62 /* CodableStructures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableStructures.swift; sourceTree = ""; }; FF7751332626A21400740D62 /* AutoCompleteLocationCustomTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteLocationCustomTableViewCell.swift; sourceTree = ""; }; FF885A6326149EAB00F0FAFF /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; + FF89AEB326636BC700BCC168 /* RealmDataAccessUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealmDataAccessUtility.swift; sourceTree = ""; }; FFB1CC82262B4CC600A692FE /* FileReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileReader.swift; sourceTree = ""; }; FFBDC71E2615CBE5002779BE /* CustomCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomCollectionViewCell.swift; sourceTree = ""; }; FFBF0E262612F0370028C11B /* Keys.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Keys.json; sourceTree = ""; }; @@ -51,48 +61,111 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + AB2F4E0D9663AF69B2166079 /* Pods_OpenWeatherApp.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - FFD71E2B2611D41D00C8BFA5 = { + A99BA00F825FF52753A31F17 /* Frameworks */ = { isa = PBXGroup; children = ( - FF517CD72623629600F1B1EF /* Readme.md */, - FFD71E362611D41D00C8BFA5 /* OpenWeatherApp */, - FFD71E352611D41D00C8BFA5 /* Products */, + 0A948C8BFEDA0218ADB76376 /* Pods_OpenWeatherApp.framework */, ); + name = Frameworks; sourceTree = ""; }; - FFD71E352611D41D00C8BFA5 /* Products */ = { + E38BD042C4F5F2E431C16793 /* Pods */ = { isa = PBXGroup; children = ( - FFD71E342611D41D00C8BFA5 /* OpenWeatherApp.app */, + 0EFBF87E588EBAF0647186BD /* Pods-OpenWeatherApp.debug.xcconfig */, + 8CB77FEDD2E7F4F98973E887 /* Pods-OpenWeatherApp.release.xcconfig */, ); - name = Products; + path = Pods; sourceTree = ""; }; - FFD71E362611D41D00C8BFA5 /* OpenWeatherApp */ = { + FF6AB70D265E6078002E189F /* Utilities */ = { isa = PBXGroup; children = ( FFD71E372611D41D00C8BFA5 /* AppDelegate.swift */, FFD71E392611D41D00C8BFA5 /* SceneDelegate.swift */, + FFBF0E262612F0370028C11B /* Keys.json */, + FFB1CC82262B4CC600A692FE /* FileReader.swift */, + FF6AB718265E696B002E189F /* InternetConnectionCheck.swift */, + ); + path = Utilities; + sourceTree = ""; + }; + FF6AB70E265E60AF002E189F /* Controllers */ = { + isa = PBXGroup; + children = ( FFD71E4E2611D6AA00C8BFA5 /* HomeViewController.swift */, - FFD71E3D2611D41D00C8BFA5 /* Main.storyboard */, FF61FD5E2622AEF000063A50 /* SearchCityNameViewController.swift */, - FF7751332626A21400740D62 /* AutoCompleteLocationCustomTableViewCell.swift */, - FF7751302626A0CD00740D62 /* CodableStructures.swift */, - FFBDC71E2615CBE5002779BE /* CustomCollectionViewCell.swift */, FFD71E522611DAA700C8BFA5 /* WeeklyDataViewController.swift */, + ); + path = Controllers; + sourceTree = ""; + }; + FF6AB70F265E60C7002E189F /* Model */ = { + isa = PBXGroup; + children = ( FF885A6326149EAB00F0FAFF /* Extensions.swift */, + FF7751302626A0CD00740D62 /* CodableStructures.swift */, + FF6DF6CE265B7BE100DCBD6D /* RealmClasses.swift */, + FF89AEB326636BC700BCC168 /* RealmDataAccessUtility.swift */, + ); + path = Model; + sourceTree = ""; + }; + FF6AB710265E60E1002E189F /* View */ = { + isa = PBXGroup; + children = ( + FF7751332626A21400740D62 /* AutoCompleteLocationCustomTableViewCell.swift */, + FFBDC71E2615CBE5002779BE /* CustomCollectionViewCell.swift */, FFD71E592611E50200C8BFA5 /* CustomTableViewCell.swift */, + ); + path = View; + sourceTree = ""; + }; + FF6AB712265E6100002E189F /* Assets */ = { + isa = PBXGroup; + children = ( + FFD71E3D2611D41D00C8BFA5 /* Main.storyboard */, FFD71E402611D41E00C8BFA5 /* Assets.xcassets */, FFD71E422611D41E00C8BFA5 /* LaunchScreen.storyboard */, + ); + path = Assets; + sourceTree = ""; + }; + FFD71E2B2611D41D00C8BFA5 = { + isa = PBXGroup; + children = ( + FF517CD72623629600F1B1EF /* Readme.md */, + FFD71E362611D41D00C8BFA5 /* OpenWeatherApp */, + FFD71E352611D41D00C8BFA5 /* Products */, + E38BD042C4F5F2E431C16793 /* Pods */, + A99BA00F825FF52753A31F17 /* Frameworks */, + ); + sourceTree = ""; + }; + FFD71E352611D41D00C8BFA5 /* Products */ = { + isa = PBXGroup; + children = ( + FFD71E342611D41D00C8BFA5 /* OpenWeatherApp.app */, + ); + name = Products; + sourceTree = ""; + }; + FFD71E362611D41D00C8BFA5 /* OpenWeatherApp */ = { + isa = PBXGroup; + children = ( + FF6AB70D265E6078002E189F /* Utilities */, + FF6AB712265E6100002E189F /* Assets */, + FF6AB70F265E60C7002E189F /* Model */, + FF6AB710265E60E1002E189F /* View */, + FF6AB70E265E60AF002E189F /* Controllers */, FFD71E452611D41E00C8BFA5 /* Info.plist */, - FFBF0E262612F0370028C11B /* Keys.json */, - FFB1CC82262B4CC600A692FE /* FileReader.swift */, ); path = OpenWeatherApp; sourceTree = ""; @@ -104,9 +177,11 @@ isa = PBXNativeTarget; buildConfigurationList = FFD71E482611D41E00C8BFA5 /* Build configuration list for PBXNativeTarget "OpenWeatherApp" */; buildPhases = ( + A61E3EA94E2B98030BCF8F2A /* [CP] Check Pods Manifest.lock */, FFD71E302611D41D00C8BFA5 /* Sources */, FFD71E312611D41D00C8BFA5 /* Frameworks */, FFD71E322611D41D00C8BFA5 /* Resources */, + B0250FEAD9314E3C0B2BCF1D /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -164,6 +239,48 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + A61E3EA94E2B98030BCF8F2A /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-OpenWeatherApp-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + B0250FEAD9314E3C0B2BCF1D /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-OpenWeatherApp/Pods-OpenWeatherApp-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-OpenWeatherApp/Pods-OpenWeatherApp-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-OpenWeatherApp/Pods-OpenWeatherApp-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ FFD71E302611D41D00C8BFA5 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -175,10 +292,13 @@ FF7751312626A0CD00740D62 /* CodableStructures.swift in Sources */, FFBDC71F2615CBE5002779BE /* CustomCollectionViewCell.swift in Sources */, FFB1CC83262B4CC600A692FE /* FileReader.swift in Sources */, + FF6AB719265E696B002E189F /* InternetConnectionCheck.swift in Sources */, FF61FD5F2622AEF000063A50 /* SearchCityNameViewController.swift in Sources */, FFD71E3A2611D41D00C8BFA5 /* SceneDelegate.swift in Sources */, + FF6DF6CF265B7BE100DCBD6D /* RealmClasses.swift in Sources */, FFD71E4F2611D6AA00C8BFA5 /* HomeViewController.swift in Sources */, FFD71E532611DAA700C8BFA5 /* WeeklyDataViewController.swift in Sources */, + FF89AEB426636BC700BCC168 /* RealmDataAccessUtility.swift in Sources */, FF885A6426149EAB00F0FAFF /* Extensions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -323,6 +443,7 @@ }; FFD71E492611D41E00C8BFA5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 0EFBF87E588EBAF0647186BD /* Pods-OpenWeatherApp.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -343,6 +464,7 @@ }; FFD71E4A2611D41E00C8BFA5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 8CB77FEDD2E7F4F98973E887 /* Pods-OpenWeatherApp.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; diff --git a/OpenWeatherApp.xcworkspace/contents.xcworkspacedata b/OpenWeatherApp.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..856bc7d --- /dev/null +++ b/OpenWeatherApp.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/OpenWeatherApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/OpenWeatherApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/OpenWeatherApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/OpenWeatherApp.xcworkspace/xcuserdata/fahimrahman.xcuserdatad/UserInterfaceState.xcuserstate b/OpenWeatherApp.xcworkspace/xcuserdata/fahimrahman.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..4356473 Binary files /dev/null and b/OpenWeatherApp.xcworkspace/xcuserdata/fahimrahman.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/OpenWeatherApp.xcworkspace/xcuserdata/fahimrahman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/OpenWeatherApp.xcworkspace/xcuserdata/fahimrahman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..62762bf --- /dev/null +++ b/OpenWeatherApp.xcworkspace/xcuserdata/fahimrahman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/OpenWeatherApp/Assets.xcassets/01d.imageset/01d.png b/OpenWeatherApp/Assets/Assets.xcassets/01d.imageset/01d.png similarity index 100% rename from OpenWeatherApp/Assets.xcassets/01d.imageset/01d.png rename to OpenWeatherApp/Assets/Assets.xcassets/01d.imageset/01d.png diff --git a/OpenWeatherApp/Assets.xcassets/01d.imageset/Contents.json b/OpenWeatherApp/Assets/Assets.xcassets/01d.imageset/Contents.json similarity index 100% rename from OpenWeatherApp/Assets.xcassets/01d.imageset/Contents.json rename to OpenWeatherApp/Assets/Assets.xcassets/01d.imageset/Contents.json diff --git a/OpenWeatherApp/Assets.xcassets/01n.imageset/01n.png b/OpenWeatherApp/Assets/Assets.xcassets/01n.imageset/01n.png similarity index 100% rename from OpenWeatherApp/Assets.xcassets/01n.imageset/01n.png rename to OpenWeatherApp/Assets/Assets.xcassets/01n.imageset/01n.png diff --git a/OpenWeatherApp/Assets.xcassets/01n.imageset/Contents.json b/OpenWeatherApp/Assets/Assets.xcassets/01n.imageset/Contents.json similarity index 100% rename from OpenWeatherApp/Assets.xcassets/01n.imageset/Contents.json rename to OpenWeatherApp/Assets/Assets.xcassets/01n.imageset/Contents.json diff --git a/OpenWeatherApp/Assets.xcassets/02d.imageset/02d.png b/OpenWeatherApp/Assets/Assets.xcassets/02d.imageset/02d.png similarity index 100% rename from OpenWeatherApp/Assets.xcassets/02d.imageset/02d.png rename to OpenWeatherApp/Assets/Assets.xcassets/02d.imageset/02d.png diff --git a/OpenWeatherApp/Assets.xcassets/02d.imageset/Contents.json b/OpenWeatherApp/Assets/Assets.xcassets/02d.imageset/Contents.json similarity index 100% rename from OpenWeatherApp/Assets.xcassets/02d.imageset/Contents.json rename to OpenWeatherApp/Assets/Assets.xcassets/02d.imageset/Contents.json diff --git a/OpenWeatherApp/Assets.xcassets/02n.imageset/02n.png b/OpenWeatherApp/Assets/Assets.xcassets/02n.imageset/02n.png similarity index 100% rename from OpenWeatherApp/Assets.xcassets/02n.imageset/02n.png rename to OpenWeatherApp/Assets/Assets.xcassets/02n.imageset/02n.png diff --git a/OpenWeatherApp/Assets.xcassets/02n.imageset/Contents.json b/OpenWeatherApp/Assets/Assets.xcassets/02n.imageset/Contents.json similarity index 100% rename from OpenWeatherApp/Assets.xcassets/02n.imageset/Contents.json rename to OpenWeatherApp/Assets/Assets.xcassets/02n.imageset/Contents.json diff --git a/OpenWeatherApp/Assets.xcassets/03d.imageset/03d.png b/OpenWeatherApp/Assets/Assets.xcassets/03d.imageset/03d.png similarity index 100% rename from OpenWeatherApp/Assets.xcassets/03d.imageset/03d.png rename to OpenWeatherApp/Assets/Assets.xcassets/03d.imageset/03d.png diff --git a/OpenWeatherApp/Assets.xcassets/03d.imageset/Contents.json b/OpenWeatherApp/Assets/Assets.xcassets/03d.imageset/Contents.json similarity index 100% rename from OpenWeatherApp/Assets.xcassets/03d.imageset/Contents.json rename to OpenWeatherApp/Assets/Assets.xcassets/03d.imageset/Contents.json diff --git a/OpenWeatherApp/Assets.xcassets/03n.imageset/03n.png b/OpenWeatherApp/Assets/Assets.xcassets/03n.imageset/03n.png similarity index 100% rename from OpenWeatherApp/Assets.xcassets/03n.imageset/03n.png rename to OpenWeatherApp/Assets/Assets.xcassets/03n.imageset/03n.png diff --git a/OpenWeatherApp/Assets.xcassets/03n.imageset/Contents.json b/OpenWeatherApp/Assets/Assets.xcassets/03n.imageset/Contents.json similarity index 100% rename from OpenWeatherApp/Assets.xcassets/03n.imageset/Contents.json rename to OpenWeatherApp/Assets/Assets.xcassets/03n.imageset/Contents.json diff --git a/OpenWeatherApp/Assets.xcassets/04d.imageset/04d.png b/OpenWeatherApp/Assets/Assets.xcassets/04d.imageset/04d.png similarity index 100% rename from OpenWeatherApp/Assets.xcassets/04d.imageset/04d.png rename to OpenWeatherApp/Assets/Assets.xcassets/04d.imageset/04d.png diff --git a/OpenWeatherApp/Assets.xcassets/04d.imageset/Contents.json b/OpenWeatherApp/Assets/Assets.xcassets/04d.imageset/Contents.json similarity index 100% rename from OpenWeatherApp/Assets.xcassets/04d.imageset/Contents.json rename to OpenWeatherApp/Assets/Assets.xcassets/04d.imageset/Contents.json diff --git a/OpenWeatherApp/Assets.xcassets/04n.imageset/04n.png b/OpenWeatherApp/Assets/Assets.xcassets/04n.imageset/04n.png similarity index 100% rename from OpenWeatherApp/Assets.xcassets/04n.imageset/04n.png rename to OpenWeatherApp/Assets/Assets.xcassets/04n.imageset/04n.png diff --git a/OpenWeatherApp/Assets.xcassets/04n.imageset/Contents.json b/OpenWeatherApp/Assets/Assets.xcassets/04n.imageset/Contents.json similarity index 100% rename from OpenWeatherApp/Assets.xcassets/04n.imageset/Contents.json rename to OpenWeatherApp/Assets/Assets.xcassets/04n.imageset/Contents.json diff --git a/OpenWeatherApp/Assets.xcassets/09d.imageset/09d.png b/OpenWeatherApp/Assets/Assets.xcassets/09d.imageset/09d.png similarity index 100% rename from OpenWeatherApp/Assets.xcassets/09d.imageset/09d.png rename to OpenWeatherApp/Assets/Assets.xcassets/09d.imageset/09d.png diff --git a/OpenWeatherApp/Assets.xcassets/09d.imageset/Contents.json b/OpenWeatherApp/Assets/Assets.xcassets/09d.imageset/Contents.json similarity index 100% rename from OpenWeatherApp/Assets.xcassets/09d.imageset/Contents.json rename to OpenWeatherApp/Assets/Assets.xcassets/09d.imageset/Contents.json diff --git a/OpenWeatherApp/Assets.xcassets/09n.imageset/09n.png b/OpenWeatherApp/Assets/Assets.xcassets/09n.imageset/09n.png similarity index 100% rename from OpenWeatherApp/Assets.xcassets/09n.imageset/09n.png rename to OpenWeatherApp/Assets/Assets.xcassets/09n.imageset/09n.png diff --git a/OpenWeatherApp/Assets.xcassets/09n.imageset/Contents.json b/OpenWeatherApp/Assets/Assets.xcassets/09n.imageset/Contents.json similarity index 100% rename from OpenWeatherApp/Assets.xcassets/09n.imageset/Contents.json rename to OpenWeatherApp/Assets/Assets.xcassets/09n.imageset/Contents.json diff --git a/OpenWeatherApp/Assets.xcassets/10d.imageset/10d.png b/OpenWeatherApp/Assets/Assets.xcassets/10d.imageset/10d.png similarity index 100% rename from OpenWeatherApp/Assets.xcassets/10d.imageset/10d.png rename to OpenWeatherApp/Assets/Assets.xcassets/10d.imageset/10d.png diff --git a/OpenWeatherApp/Assets.xcassets/10d.imageset/Contents.json b/OpenWeatherApp/Assets/Assets.xcassets/10d.imageset/Contents.json similarity index 100% rename from OpenWeatherApp/Assets.xcassets/10d.imageset/Contents.json rename to OpenWeatherApp/Assets/Assets.xcassets/10d.imageset/Contents.json diff --git a/OpenWeatherApp/Assets.xcassets/10n.imageset/10n.png b/OpenWeatherApp/Assets/Assets.xcassets/10n.imageset/10n.png similarity index 100% rename from OpenWeatherApp/Assets.xcassets/10n.imageset/10n.png rename to OpenWeatherApp/Assets/Assets.xcassets/10n.imageset/10n.png diff --git a/OpenWeatherApp/Assets.xcassets/10n.imageset/Contents.json b/OpenWeatherApp/Assets/Assets.xcassets/10n.imageset/Contents.json similarity index 100% rename from OpenWeatherApp/Assets.xcassets/10n.imageset/Contents.json rename to OpenWeatherApp/Assets/Assets.xcassets/10n.imageset/Contents.json diff --git a/OpenWeatherApp/Assets.xcassets/11d.imageset/11d.png b/OpenWeatherApp/Assets/Assets.xcassets/11d.imageset/11d.png similarity index 100% rename from OpenWeatherApp/Assets.xcassets/11d.imageset/11d.png rename to OpenWeatherApp/Assets/Assets.xcassets/11d.imageset/11d.png diff --git a/OpenWeatherApp/Assets.xcassets/11d.imageset/Contents.json b/OpenWeatherApp/Assets/Assets.xcassets/11d.imageset/Contents.json similarity index 100% rename from OpenWeatherApp/Assets.xcassets/11d.imageset/Contents.json rename to OpenWeatherApp/Assets/Assets.xcassets/11d.imageset/Contents.json diff --git a/OpenWeatherApp/Assets.xcassets/11n.imageset/11n.png b/OpenWeatherApp/Assets/Assets.xcassets/11n.imageset/11n.png similarity index 100% rename from OpenWeatherApp/Assets.xcassets/11n.imageset/11n.png rename to OpenWeatherApp/Assets/Assets.xcassets/11n.imageset/11n.png diff --git a/OpenWeatherApp/Assets.xcassets/11n.imageset/Contents.json b/OpenWeatherApp/Assets/Assets.xcassets/11n.imageset/Contents.json similarity index 100% rename from OpenWeatherApp/Assets.xcassets/11n.imageset/Contents.json rename to OpenWeatherApp/Assets/Assets.xcassets/11n.imageset/Contents.json diff --git a/OpenWeatherApp/Assets.xcassets/13d.imageset/13d.png b/OpenWeatherApp/Assets/Assets.xcassets/13d.imageset/13d.png similarity index 100% rename from OpenWeatherApp/Assets.xcassets/13d.imageset/13d.png rename to OpenWeatherApp/Assets/Assets.xcassets/13d.imageset/13d.png diff --git a/OpenWeatherApp/Assets.xcassets/13d.imageset/Contents.json b/OpenWeatherApp/Assets/Assets.xcassets/13d.imageset/Contents.json similarity index 100% rename from OpenWeatherApp/Assets.xcassets/13d.imageset/Contents.json rename to OpenWeatherApp/Assets/Assets.xcassets/13d.imageset/Contents.json diff --git a/OpenWeatherApp/Assets.xcassets/13n.imageset/13n.png b/OpenWeatherApp/Assets/Assets.xcassets/13n.imageset/13n.png similarity index 100% rename from OpenWeatherApp/Assets.xcassets/13n.imageset/13n.png rename to OpenWeatherApp/Assets/Assets.xcassets/13n.imageset/13n.png diff --git a/OpenWeatherApp/Assets.xcassets/13n.imageset/Contents.json b/OpenWeatherApp/Assets/Assets.xcassets/13n.imageset/Contents.json similarity index 100% rename from OpenWeatherApp/Assets.xcassets/13n.imageset/Contents.json rename to OpenWeatherApp/Assets/Assets.xcassets/13n.imageset/Contents.json diff --git a/OpenWeatherApp/Assets.xcassets/50d.imageset/50d.png b/OpenWeatherApp/Assets/Assets.xcassets/50d.imageset/50d.png similarity index 100% rename from OpenWeatherApp/Assets.xcassets/50d.imageset/50d.png rename to OpenWeatherApp/Assets/Assets.xcassets/50d.imageset/50d.png diff --git a/OpenWeatherApp/Assets.xcassets/50d.imageset/Contents.json b/OpenWeatherApp/Assets/Assets.xcassets/50d.imageset/Contents.json similarity index 100% rename from OpenWeatherApp/Assets.xcassets/50d.imageset/Contents.json rename to OpenWeatherApp/Assets/Assets.xcassets/50d.imageset/Contents.json diff --git a/OpenWeatherApp/Assets.xcassets/50n.imageset/50n.png b/OpenWeatherApp/Assets/Assets.xcassets/50n.imageset/50n.png similarity index 100% rename from OpenWeatherApp/Assets.xcassets/50n.imageset/50n.png rename to OpenWeatherApp/Assets/Assets.xcassets/50n.imageset/50n.png diff --git a/OpenWeatherApp/Assets.xcassets/50n.imageset/Contents.json b/OpenWeatherApp/Assets/Assets.xcassets/50n.imageset/Contents.json similarity index 100% rename from OpenWeatherApp/Assets.xcassets/50n.imageset/Contents.json rename to OpenWeatherApp/Assets/Assets.xcassets/50n.imageset/Contents.json diff --git a/OpenWeatherApp/Assets.xcassets/AccentColor.colorset/Contents.json b/OpenWeatherApp/Assets/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from OpenWeatherApp/Assets.xcassets/AccentColor.colorset/Contents.json rename to OpenWeatherApp/Assets/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/OpenWeatherApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/OpenWeatherApp/Assets/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from OpenWeatherApp/Assets.xcassets/AppIcon.appiconset/Contents.json rename to OpenWeatherApp/Assets/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/OpenWeatherApp/Assets.xcassets/Contents.json b/OpenWeatherApp/Assets/Assets.xcassets/Contents.json similarity index 100% rename from OpenWeatherApp/Assets.xcassets/Contents.json rename to OpenWeatherApp/Assets/Assets.xcassets/Contents.json diff --git a/OpenWeatherApp/Assets.xcassets/background.imageset/Contents.json b/OpenWeatherApp/Assets/Assets.xcassets/background.imageset/Contents.json similarity index 100% rename from OpenWeatherApp/Assets.xcassets/background.imageset/Contents.json rename to OpenWeatherApp/Assets/Assets.xcassets/background.imageset/Contents.json diff --git a/OpenWeatherApp/Assets.xcassets/background.imageset/unnamed.jpeg b/OpenWeatherApp/Assets/Assets.xcassets/background.imageset/unnamed.jpeg similarity index 100% rename from OpenWeatherApp/Assets.xcassets/background.imageset/unnamed.jpeg rename to OpenWeatherApp/Assets/Assets.xcassets/background.imageset/unnamed.jpeg diff --git a/OpenWeatherApp/Assets.xcassets/mapMarker.imageset/Contents.json b/OpenWeatherApp/Assets/Assets.xcassets/mapMarker.imageset/Contents.json similarity index 100% rename from OpenWeatherApp/Assets.xcassets/mapMarker.imageset/Contents.json rename to OpenWeatherApp/Assets/Assets.xcassets/mapMarker.imageset/Contents.json diff --git a/OpenWeatherApp/Assets.xcassets/mapMarker.imageset/mapMarker.png b/OpenWeatherApp/Assets/Assets.xcassets/mapMarker.imageset/mapMarker.png similarity index 100% rename from OpenWeatherApp/Assets.xcassets/mapMarker.imageset/mapMarker.png rename to OpenWeatherApp/Assets/Assets.xcassets/mapMarker.imageset/mapMarker.png diff --git a/OpenWeatherApp/Base.lproj/LaunchScreen.storyboard b/OpenWeatherApp/Assets/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from OpenWeatherApp/Base.lproj/LaunchScreen.storyboard rename to OpenWeatherApp/Assets/Base.lproj/LaunchScreen.storyboard diff --git a/OpenWeatherApp/Base.lproj/Main.storyboard b/OpenWeatherApp/Assets/Base.lproj/Main.storyboard similarity index 100% rename from OpenWeatherApp/Base.lproj/Main.storyboard rename to OpenWeatherApp/Assets/Base.lproj/Main.storyboard diff --git a/OpenWeatherApp/Controllers/HomeViewController.swift b/OpenWeatherApp/Controllers/HomeViewController.swift new file mode 100644 index 0000000..50c4a00 --- /dev/null +++ b/OpenWeatherApp/Controllers/HomeViewController.swift @@ -0,0 +1,510 @@ +// +// HomeViewController.swift +// OpenWeatherApp +// +// Created by Fahim Rahman on 29/3/21. +// + +import UIKit +import CoreLocation +import RealmSwift +import Alamofire + +class HomeViewController: UIViewController { + + @IBOutlet weak var locationNameLabel: UILabel! + @IBOutlet weak var spinner: UIActivityIndicatorView! + @IBOutlet weak var presentDayDateNTime: UILabel! + @IBOutlet weak var presentDayTemp: UILabel! + @IBOutlet weak var presentDayWeatherIcon: UIImageView! + @IBOutlet weak var presentDaySunriseTime: UILabel! + @IBOutlet weak var presentDaySunsetTime: UILabel! + @IBOutlet weak var presentDayFeels: UILabel! + @IBOutlet weak var presentdayWeatherDescription: UILabel! + @IBOutlet weak var collection_View: UICollectionView! + + var locationManager = CLLocationManager() + var currentLocation: CLLocation? + var latitude = 0.0 + var longitude = 0.0 + var openWeatherMap_access_token = "" + var nextSevenDaysForecast = [Daily]() + // var presentDayData = [Daily]() + var presentDayForecast = Current(dt: 0, sunrise: 0, sunset: 0, temp: 0.0, feels_like: 0.0, weather: [Weather(id: 0, main: "", description: "", icon: "")]) + var presentDayHourlyForecast = [Hourly]() + var timer = Timer() + var dynamicPresentDayDateTime = 0 + var timezoneIdentifier = "" + var locationName = "" + let dateFormatter = DateFormatter() + var isAppEverWentInBackgroundState = false + var timeWhenAppWentInBackground = "" + var timeWhenAppComeInForeground = "" +// var presentDayHourlyForecastFromRealm = Array() + + // MARK:- viewDidLoad() Part + override func viewDidLoad() { + super.viewDidLoad() + + self.view.backgroundColor = UIColor(patternImage: UIImage(named: "background.jpeg")!) + + let fileReader = FileReader() + if let apiData = fileReader.readSecretKeyFile(forFileName: "Keys") { + if let tempData = fileReader.parseSecretKeyFile(jsonData: apiData, keyFor: "openweathermap") { + openWeatherMap_access_token = tempData + } + } + + if RealmDataAccessUtility.checkIfWeatherForcastsPresentInRealm() { + loadDataInHomeViewFromRealm() + weatherForecastDataDisplay() + } + + checkInternetConnectivity() + + // if let userSearchedLocationName = UserDefaults.standard.string(forKey: "userSelectedPlacesnameValue") { + // retriveSavedLocationData(for: userSearchedLocationName) + // } else { + // checkLocationServies() + // } + + collection_View.dataSource = self + + NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterInBackgroundState), name: UIApplication.willResignActiveNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterInForegroundState), name: UIApplication.willEnterForegroundNotification, object: nil) + } + + private func checkInternetConnectivity() { + if InternetConnectionCheck.ConnectionStatus() { + checkLocationServies() +// callFetchAPIData() + } else { + displayAlertWithButton(dialogTitle: "No Internet", dialogMessage: "Please connect to Wi-Fi or enable Mobile Data to see the Weather Forecasts", buttonTitle: "Close") + DispatchQueue.main.async { + self.spinner.stopAnimating() + } + } + } + + // private func retriveSavedLocationData(for place: String) { + // locationName = place + // latitude = UserDefaults.standard.double(forKey: "userSelectedPlacesLatitudeValue") + // longitude = UserDefaults.standard.double(forKey: "userSelectedPlacesLongitudeValue") + // } + + private func retriveSavedLocationDataFromRealm() { + do { + let realmReference = try Realm() + let fetchedDataFromRealm = realmReference.objects(CityNameAndLocationInfoInRealm.self) + + if fetchedDataFromRealm.count == 1 { + locationName = fetchedDataFromRealm[0].stored_cityName + latitude = fetchedDataFromRealm[0].stored_latitude + longitude = fetchedDataFromRealm[0].stored_longitude + } + } catch { + print("Error in Realm Integration in HomeViewController()") + } + } + + // MARK: - API Calling and Display + private func callFetchAPIData() { + fetchAPIData(completionHandler: { [weak self] (weather) in + guard let self = self else { + return + } + + self.presentDayForecast = weather.current + self.nextSevenDaysForecast = weather.daily + self.presentDayHourlyForecast = weather.hourly + self.timezoneIdentifier = weather.timezone + + self.modifyDailyDataFromAPIResponse() + self.modifyHourlyDataFromAPIResponse() + self.weatherForecastDataDisplay() + + RealmDataAccessUtility.deleteWeatherDetailsClassData() + RealmDataAccessUtility.savePresentDayWeatherForecastFrom(presentDayForecast: self.presentDayForecast) + RealmDataAccessUtility.savePresentDaysHourlyWeatherForecastFrom(data: self.presentDayHourlyForecast) + RealmDataAccessUtility.saveNextSevenDaysForecastFrom(data: self.nextSevenDaysForecast) + RealmDataAccessUtility.savePresentDayTime(from: self.timezoneIdentifier) + }) + } + + private func modifyDailyDataFromAPIResponse() { + if !self.nextSevenDaysForecast.isEmpty { + // self.presentDayData = Array(self.nextSevenDaysData.prefix(1)) + self.nextSevenDaysForecast.removeFirst() + } + } + + private func modifyHourlyDataFromAPIResponse() { + if !self.presentDayHourlyForecast.isEmpty { + print("Before Slicing", self.presentDayHourlyForecast.count) + self.presentDayHourlyForecast = Array(self.presentDayHourlyForecast[0...23]) + print("After Slicing", self.presentDayHourlyForecast.count) + } + } + + private func loadDataInHomeViewFromRealm() { + do { + let realmReference = try Realm() + + let fetchedCurrentDataFromRealm = realmReference.objects(PresentDayWeatherForecastInRealm.self) + let fetchedWeatherInfo = realmReference.objects(CityNameAndLocationInfoInRealm.self) + let fetchedHourlyDataFromRealm = realmReference.objects(PresentDayHourlyWeatherForecastInRealm.self) + + presentDayForecast.dt = fetchedCurrentDataFromRealm[0].dt + presentDayForecast.sunrise = fetchedCurrentDataFromRealm[0].sunrise + presentDayForecast.sunset = fetchedCurrentDataFromRealm[0].sunset + presentDayForecast.temp = fetchedCurrentDataFromRealm[0].temp + presentDayForecast.feels_like = fetchedCurrentDataFromRealm[0].feels_like + presentDayForecast.weather[0].description = fetchedCurrentDataFromRealm[0].weather[0].weather_description + presentDayForecast.weather[0].icon = fetchedCurrentDataFromRealm[0].weather[0].weather_icon + + locationName = fetchedWeatherInfo[0].stored_cityName + + for _ in 0...23 { + presentDayHourlyForecast.append(Hourly(dt: 0, temp: 0.0, feels_like: 0.0, weather: [Weather(id: 0, main: "", description: "", icon: "")])) + } + + for item in 0.. \(timeWhenAppWentInBackground)") + // timer.invalidate() + } + + @objc func appWillEnterInForegroundState() { + // fetchCurrentLocation() + // timer.invalidate() + // checkLocationServies() + + dateFormatter.dateFormat = "h:mm:ss" + timeWhenAppComeInForeground = dateFormatter.string(from: Date()) + + if let timeSpent = checkHowMuchTimeAppWasInBackground(), timeSpent >= 5.0 { + checkLocationServies() + } + } + + private func checkHowMuchTimeAppWasInBackground() -> Double? { + if isAppEverWentInBackgroundState { + guard let timeAtBackground = dateFormatter.date(from: timeWhenAppWentInBackground), + let timeAtForeground = dateFormatter.date(from: timeWhenAppComeInForeground) else { + print("Error in time -> isAppEverWentInBackgroundState check") + return nil + } + let interval = timeAtForeground.timeIntervalSince(timeAtBackground) + let minute = interval / 60 + print("after \(minute)") + return minute + } + return nil + } +} + +// MARK:- Location Feteching Part +extension HomeViewController: CLLocationManagerDelegate { + func checkLocationServies() { + if CLLocationManager.locationServicesEnabled() { + setupLocationManager() + checkLocationAuthorization() + } else { + displayAlertWithButton(dialogTitle: "Turn on Location Services", dialogMessage: "Please Turn \"Location Services\" On From \"Settings -> Privacy -> Location Services -> Location Sevices\".", buttonTitle: "Close") + } + } + + func setupLocationManager() { + locationManager.delegate = self + locationManager.desiredAccuracy = kCLLocationAccuracyBest + } + + func checkLocationAuthorization() { + switch CLLocationManager.authorizationStatus() { + case .authorizedWhenInUse: + locationManager.requestLocation() + break + case .denied: + displayAlertWithButton(dialogTitle: "Turn on Location Access For this App", dialogMessage: "Please Turn \"Location Access Permission\" On From \"Settings -> Privacy -> Location Services -> OpenWeatherApp -> While Using the App\".", buttonTitle: "Okay") + case .notDetermined: + locationManager.requestWhenInUseAuthorization() + case .restricted: + displayAlertWithButton(dialogTitle: "Restricted By User", dialogMessage: "This is possibly due to active restrictions such as parental controls being in place.", buttonTitle: "Close") + case .authorizedAlways: + locationManager.requestLocation() + default: + displayAlertWithButton(dialogTitle: "Unknown Case!", dialogMessage: "This Permission is not handled in developing time.", buttonTitle: "Okay") + } + } + + func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { + checkLocationAuthorization() + } + + func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + currentLocation = locations.last + + if let lat = currentLocation?.coordinate.latitude { + self.latitude = lat + print("CLLocationManager - Latitude: ", self.latitude) + } + + if let lon = currentLocation?.coordinate.longitude { + self.longitude = lon + print("CLLocationManager - Longitude: ", self.longitude) + } + callFetchAPIData() + callReverseGeoCoder() + } + + func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { + print(error) + displayAlertWithButton(dialogTitle: "Error in Fetching Location", dialogMessage: "Error Occured: \(error). Please Check Your Location On the Settings -> Privacy -> Location Services", buttonTitle: "Close") + } + + func displayAlertWithButton(dialogTitle title: String, dialogMessage message: String, buttonTitle name: String) { + let dialogMessage = UIAlertController(title: title, message: message, preferredStyle: .alert) + let okButtonInAlert = UIAlertAction(title: name, style: .default, handler: nil) + dialogMessage.addAction(okButtonInAlert) + self.present(dialogMessage, animated: true, completion: nil) + } + + private func callReverseGeoCoder() { + let geoCoder = CLGeocoder() + let userCurrentLocation = CLLocation(latitude: self.latitude, longitude: self.longitude) + geoCoder.reverseGeocodeLocation(userCurrentLocation, completionHandler: { [weak self] (placemarks, error) in + guard let self = self else { + return + } + + if let _ = error { + return + } + + guard let placemark = placemarks?.first else { + return + } + + if let placeName = placemark.locality, let placeCountry = placemark.country { + print(placeName) + self.locationName = "\(placeName), \(placeCountry)" + + DispatchQueue.main.async { + self.locationNameLabel.text = self.locationName + } + + RealmDataAccessUtility.saveCityNameAndCoordinatesForLocation(name: self.locationName, latitude: self.latitude, longitude: self.longitude) + + } else { + print("Error in callReverseGeoCoder()") + } + }) + } +} + +// MARK:- Define fetchAPIData() +extension HomeViewController { + func fetchAPIData(completionHandler: @escaping (WeatherData) -> ()) { + let baseAddress = "https://api.openweathermap.org/data/2.5/onecall?" + let lat = "lat=\(latitude)" + let lon = "&lon=\(longitude)" + let openWeatherMapAPIKEY = "&appid=" + openWeatherMap_access_token + let excludesFromAPIresponse = "&exclude=minutely,alerts" + let unitsOfDataFromAPIResponse = "&units=metric" + + print("Latitude in fetchAPIData()", latitude) + print("Longitude in fetchAPIData()", longitude) + + let urlString = baseAddress + lat + lon + unitsOfDataFromAPIResponse + excludesFromAPIresponse + openWeatherMapAPIKEY + + print(urlString) + + AF.request(urlString).responseData { response in + if let data = response.data { + do { + let result = try JSONDecoder().decode(WeatherData.self, from: data) + completionHandler(result) + } catch { + print("Error in Data Decoding in fetchAPIData()", error) + } + } + } + +// guard let url = URL(string: urlString) else { +// print("Error In URL construction in fetchAPIData()") +// return +// } +// +// let task = URLSession.shared.dataTask(with: url) { (data, response, error) in +// guard let data = data, error == nil else { +// print("Error Occured in Retrieving Data in fetchAPIData()") +// return +// } +// +// if let jsonData = try? JSONSerialization.jsonObject(with: data, options: []) { +// print(jsonData) +// } +// +// do { +// let result = try JSONDecoder().decode(WeatherData.self, from: data) +// completionHandler(result) +// } catch { +// print("Error in Data Decoding in fetchAPIData()", error) +// } +// } +// task.resume() + } +} + +// MARK:- CollectionView +extension HomeViewController: UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { +// if IsDailyWeatherDataSourceRealm() { +// return presentDayHourlyForecastFromRealm.count +// } + return presentDayHourlyForecast.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionview_cell", for: indexPath) as! CustomCollectionViewCell + cell.forecastHourlyTemp.text = "\(self.presentDayHourlyForecast[indexPath.row].temp)°C" + cell.forecastHourlyTime.text = self.presentDayHourlyForecast[indexPath.row].dt.fromUnixTimeToTime() + // cell.forecastHourlyWeatherIcon.image = UIImage(named: self.HourlyData[indexPath.row].weather[0].icon) + let urlString = "https://openweathermap.org/img/wn/" + self.presentDayHourlyForecast[indexPath.row].weather[0].icon + ".png" + if let url = URL(string: urlString) { + cell.forecastHourlyWeatherIcon.imageLoad(from: url) + } else { + print("Error in URL() in collectionView - cellForItemAt indexPath") + } + // } + return cell + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + return CGSize(width: view.frame.width, height: 180) + } + + private func IsDailyWeatherDataSourceRealm() -> Bool { + if presentDayHourlyForecast.count == 0 { + return true + } + return false + } +} + +// MARK:- Segue +extension HomeViewController { + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + let nextViewController = segue.destination as? WeeklyDataViewController + + if nextViewController != nil { + // nextViewController?.latitude = self.latitude + // nextViewController?.longitude = self.longitude + nextViewController?.nextSevenDaysData = self.nextSevenDaysForecast + } + } + + @IBAction func unwindToHomeViewController(_ sender: UIStoryboardSegue) { + // guard let userSearchedLocationName = UserDefaults.standard.string(forKey: "userSelectedPlacesnameValue") else { + // print("Error in retriving data from userDefaults") + // return + // } + + // retriveSavedLocationData(for: userSearchedLocationName) + retriveSavedLocationDataFromRealm() + + + DispatchQueue.main.async { + self.spinner.startAnimating() + } + + callFetchAPIData() + + // if let sourceVC = sender.source as? SearchCityNameViewController { + // // store data in the variables + // locationName = sourceVC.userSelectedPlacesname + // latitude = sourceVC.userSelectedPlacesLatitude + // longitude = sourceVC.userSelectedPlacesLongitude + // + // DispatchQueue.main.async { + // self.spinner.startAnimating() + // } + // + // // Call the FetchAPIData() + // timer.invalidate() + // callFetchAPIData() + // } + } +} diff --git a/OpenWeatherApp/SearchCityNameViewController.swift b/OpenWeatherApp/Controllers/SearchCityNameViewController.swift similarity index 91% rename from OpenWeatherApp/SearchCityNameViewController.swift rename to OpenWeatherApp/Controllers/SearchCityNameViewController.swift index e753857..1f4dd87 100644 --- a/OpenWeatherApp/SearchCityNameViewController.swift +++ b/OpenWeatherApp/Controllers/SearchCityNameViewController.swift @@ -6,6 +6,8 @@ // import UIKit +import RealmSwift +import Network class SearchCityNameViewController: UIViewController,UISearchBarDelegate { @@ -36,6 +38,7 @@ class SearchCityNameViewController: UIViewController,UISearchBarDelegate { tableView.delegate = self tableView.dataSource = self + tableView.tableFooterView = UIView() } // MARK:- SearchBar Delegate Functions @@ -158,13 +161,15 @@ extension SearchCityNameViewController: UITableViewDataSource, UITableViewDelega func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { self.userSelectedPlacesLatitude = suggestedPlacenames[indexPath.row].geometry.coordinates[1] - UserDefaults.standard.set(userSelectedPlacesLatitude, forKey: "userSelectedPlacesLatitudeValue") +// UserDefaults.standard.set(userSelectedPlacesLatitude, forKey: "userSelectedPlacesLatitudeValue") self.userSelectedPlacesLongitude = suggestedPlacenames[indexPath.row].geometry.coordinates[0] - UserDefaults.standard.set(userSelectedPlacesLongitude, forKey: "userSelectedPlacesLongitudeValue") +// UserDefaults.standard.set(userSelectedPlacesLongitude, forKey: "userSelectedPlacesLongitudeValue") self.userSelectedPlacesname = self.suggestedPlacenames[indexPath.row].place_name ?? "Error" - UserDefaults.standard.set(userSelectedPlacesname, forKey: "userSelectedPlacesnameValue") +// UserDefaults.standard.set(userSelectedPlacesname, forKey: "userSelectedPlacesnameValue") + + RealmDataAccessUtility.saveCityNameAndCoordinatesForLocation(name: userSelectedPlacesname, latitude: userSelectedPlacesLatitude, longitude: userSelectedPlacesLongitude) performSegue(withIdentifier: "unwindSegue", sender: self) } diff --git a/OpenWeatherApp/Controllers/WeeklyDataViewController.swift b/OpenWeatherApp/Controllers/WeeklyDataViewController.swift new file mode 100644 index 0000000..be21ad1 --- /dev/null +++ b/OpenWeatherApp/Controllers/WeeklyDataViewController.swift @@ -0,0 +1,95 @@ +// +// WeeklyDataViewController.swift +// OpenWeatherApp +// +// Created by Fahim Rahman on 29/3/21. +// + +import UIKit +import RealmSwift +import Network + +class WeeklyDataViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { + + @IBOutlet weak var tableView: UITableView! + +// var latitude = 0.0 +// var longitude = 0.0 + + var nextSevenDaysData = [Daily]() +// var nextSevenDaysDataFromRealm = Array() + + // MARK:- viewDidLoad() + override func viewDidLoad() { + super.viewDidLoad() + + tableView.delegate = self + tableView.dataSource = self + + tableView.backgroundView = UIImageView(image: UIImage(named: "background.jpeg")) + + tableView.tableFooterView = UIView() + + if RealmDataAccessUtility.checkIfNextSevenDaysForecastPresentInRealm() { + do { + let realmReference = try Realm() + let fetchedNextSevenDaysData = realmReference.objects(NextSevenDaysWeatherForecastInRealm.self) +// nextSevenDaysDataFromRealm = Array(retrivedData) + + nextSevenDaysData.removeAll() + + for _ in 0...6 { + nextSevenDaysData.append(Daily(dt: 0, sunrise: 0, sunset: 0, temp: Temp(day: 0.0, min: 0.0, max: 0.0, night: 0.0, eve: 0.0, morn: 0.0), feels_like: feels_like(day: 0.0, night: 0.0, eve: 0.0, morn: 0.0), pressure: 0, humidity: 0, dew_point: 0.0, wind_speed: 0.0, wind_deg: 0, weather: [Weather(id: 0, main: "", description: "", icon: "")], clouds: 0, uvi: 0.0)) + } + + for item in 0.. Int { + return 1 + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return nextSevenDaysData.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + let cell = tableView.dequeueReusableCell(withIdentifier: "table_cell") as! CustomTableViewCell + + cell.selectionStyle = .none + + if indexPath.row <= 6 { + cell.forecastDate.text = "\(self.nextSevenDaysData[indexPath.row].dt.fromUnixTimeToDate())" + // cell.forecastSunriseTime.text = "Sunrise: " + self.NextSevenDaysData[indexPath.row].sunrise.fromUnixTimeToTime() + // cell.forecastSunsetTime.text = "Sunset: " + self.NextSevenDaysData[indexPath.row].sunset.fromUnixTimeToTime() + // cell.forecastWeatherIcon.image = UIImage(named: self.NextSevenDaysData[indexPath.row].weather[0].icon) + let urlString = "https://openweathermap.org/img/wn/" + self.nextSevenDaysData[indexPath.row].weather[0].icon + ".png" + let url = URL(string: urlString) + cell.forecastWeatherIcon.imageLoad(from: url!) + cell.forecastWeatherDescription.text = "" + self.nextSevenDaysData[indexPath.row].weather[0].description.capitalized + cell.forecastMaxTemp.text = "Max: \(self.nextSevenDaysData[indexPath.row].temp.max)°C" + cell.forecastMinTemp.text = "Min: \(self.nextSevenDaysData[indexPath.row].temp.min)°C" + // } + } + +// cell.backgroundColor = indexPath.row % 2 == 0 ? .cyan : .lightGray + + return cell + } + + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return 100 + } +} diff --git a/OpenWeatherApp/HomeViewController.swift b/OpenWeatherApp/HomeViewController.swift deleted file mode 100644 index 237cf56..0000000 --- a/OpenWeatherApp/HomeViewController.swift +++ /dev/null @@ -1,389 +0,0 @@ -// -// HomeViewController.swift -// OpenWeatherApp -// -// Created by Fahim Rahman on 29/3/21. -// - -import UIKit -import CoreLocation - -class HomeViewController: UIViewController { - - @IBOutlet weak var locationNameLabel: UILabel! - @IBOutlet weak var spinner: UIActivityIndicatorView! - @IBOutlet weak var presentDayDateNTime: UILabel! - @IBOutlet weak var presentDayTemp: UILabel! - @IBOutlet weak var presentDayWeatherIcon: UIImageView! - @IBOutlet weak var presentDaySunriseTime: UILabel! - @IBOutlet weak var presentDaySunsetTime: UILabel! - @IBOutlet weak var presentDayFeels: UILabel! - @IBOutlet weak var presentdayWeatherDescription: UILabel! - @IBOutlet weak var collection_View: UICollectionView! - - var locationManager = CLLocationManager() - var currentLocation: CLLocation? - var latitude = 0.0 - var longitude = 0.0 - var openWeatherMap_access_token = "" - var nextSevenDaysData = [Daily]() - var presentDayData = [Daily]() - var currentDayData = Current(dt: 0, sunrise: 0, sunset: 0, temp: 0.0, feels_like: 0.0, weather: []) - var hourlyData = [Hourly]() - var timer = Timer() - var dynamicCurrentDateNTime = 0 - var timezoneIdentifier = "" - var locationName = "" - let dateFormatter = DateFormatter() - var isAppEverWentInBackgroundState = false - var timeWhenAppWentInBackground = "" - var timeWhenAppComeInForeground = "" - - // MARK:- viewDidLoad() Part - override func viewDidLoad() { - super.viewDidLoad() - - self.view.backgroundColor = UIColor(patternImage: UIImage(named: "background.jpeg")!) - - let fileReader = FileReader() - if let apiData = fileReader.readSecretKeyFile(forFileName: "Keys") { - if let tempData = fileReader.parseSecretKeyFile(jsonData: apiData, keyFor: "openweathermap") { - openWeatherMap_access_token = tempData - } - } - - if let userSearchedLocationName = UserDefaults.standard.string(forKey: "userSelectedPlacesnameValue") { - retriveSavedLocationData(for: userSearchedLocationName) - } else { - checkLocationServies() - } - - callFetchAPIData() - - collection_View.dataSource = self - - NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterInBackgroundState), name: UIApplication.willResignActiveNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterInForegroundState), name: UIApplication.willEnterForegroundNotification, object: nil) - } - - private func retriveSavedLocationData(for place: String) { - locationName = place - latitude = UserDefaults.standard.double(forKey: "userSelectedPlacesLatitudeValue") - longitude = UserDefaults.standard.double(forKey: "userSelectedPlacesLongitudeValue") - } - - // MARK: - API Calling and Display - private func callFetchAPIData() { - fetchAPIData(completionHandler: { [self] (weather) in - - currentDayData = weather.current - nextSevenDaysData = weather.daily - hourlyData = weather.hourly - timezoneIdentifier = weather.timezone - - modifyDailyDataFromAPIResponse() - modifyHourlyDataFromAPIResponse() - weatherForecastDataDisplay() - }) - } - - private func modifyDailyDataFromAPIResponse() { - if !self.nextSevenDaysData.isEmpty { - self.presentDayData = Array(self.nextSevenDaysData.prefix(1)) - self.nextSevenDaysData.removeFirst() - } - } - - private func modifyHourlyDataFromAPIResponse() { - if !self.hourlyData.isEmpty { - print("Before Slicing", self.hourlyData.count) - self.hourlyData = Array(self.hourlyData[0...23]) - print("After Slicing", self.hourlyData.count) - } - } - - private func weatherForecastDataDisplay() { - DispatchQueue.main.async { - print("In Dispathch",self.currentDayData) - self.dynamicCurrentDateNTime = self.currentDayData.dt - self.getCurrentTime() -// self.presentDayDateNTime.text = self.CurrentDayData.dt.fromUnixTimeToTimeNDate() - self.presentDayTemp.text = "\(self.currentDayData.temp)°" - let url = URL(string: "https://openweathermap.org/img/wn/" + self.currentDayData.weather[0].icon + ".png") - self.presentDayWeatherIcon.imageLoad(from: url!) -// self.presentDayWeatherIcon.image = UIImage(named: self.CurrentDayData.weather[0].icon) - self.presentDaySunriseTime.text = "Sunrise: " + self.currentDayData.sunrise.fromUnixTimeToTime() - self.presentDaySunsetTime.text = "Sunset: " + self.currentDayData.sunset.fromUnixTimeToTime() - self.presentDayFeels.text = "Feels like: \(self.currentDayData.feels_like)°C" - self.presentdayWeatherDescription.text = self.currentDayData.weather[0].description.capitalized - self.locationNameLabel.text = self.locationName - - self.collection_View.reloadData() - self.spinner.stopAnimating() - } - } - - // MARK:- Real Time Clock Display - func getCurrentTime() { - timer.invalidate() - timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.currentTimeAfterFetchedTime), userInfo: nil, repeats: true) - } - - @objc func currentTimeAfterFetchedTime(currentTime : Int) { - let formatter = DateFormatter() - formatter.dateFormat = "MMM d, h:mm:ss a" -// formatter.timeZone = TimeZone(secondsFromGMT: 0) -// let offset = TimeZone.current.secondsFromGMT() -// print("Checking", offset) - formatter.timeZone = TimeZone(identifier: self.timezoneIdentifier) - - DispatchQueue.main.async { - self.presentDayDateNTime.text = formatter.string(from: Date(timeIntervalSince1970: TimeInterval(self.dynamicCurrentDateNTime))) - self.dynamicCurrentDateNTime += 1 - print(Date(timeIntervalSince1970: TimeInterval(self.dynamicCurrentDateNTime))) - } - } - - // MARK:- Notification Observer Action - @objc func appWillEnterInBackgroundState() { - print("About to go in background") - isAppEverWentInBackgroundState = true - dateFormatter.dateFormat = "h:mm:ss" - timeWhenAppWentInBackground = dateFormatter.string(from: Date()) - print("Time now -> \(timeWhenAppWentInBackground)") -// timer.invalidate() - } - - @objc func appWillEnterInForegroundState() { -// fetchCurrentLocation() -// timer.invalidate() -// checkLocationServies() - - dateFormatter.dateFormat = "h:mm:ss" - timeWhenAppComeInForeground = dateFormatter.string(from: Date()) - - if let timeSpent = checkHowMuchTimeAppWasInBackground(), timeSpent >= 1.0 { - checkLocationServies() - } - } - - private func checkHowMuchTimeAppWasInBackground() -> Double? { - if isAppEverWentInBackgroundState { - guard let timeAtBackground = dateFormatter.date(from: timeWhenAppWentInBackground), - let timeAtForeground = dateFormatter.date(from: timeWhenAppComeInForeground) else { - print("Error in time -> isAppEverWentInBackgroundState check") - return nil - } - let interval = timeAtForeground.timeIntervalSince(timeAtBackground) - let minute = interval / 60 - print("after \(minute)") - return minute - } - return nil - } -} - - -// MARK:- Location Feteching Part -extension HomeViewController: CLLocationManagerDelegate { - func checkLocationServies() { - if CLLocationManager.locationServicesEnabled() { - setupLocationManager() - checkLocationAuthorization() - } else { - displayAlertWithButton(dialogTitle: "Turn on Location Services", dialogMessage: "Please Turn \"Location Services\" On From \"Settings -> Privacy -> Location Services -> Location Sevices\".", buttonTitle: "Close") - } - } - - func setupLocationManager() { - locationManager.delegate = self - locationManager.desiredAccuracy = kCLLocationAccuracyBest - } - - func checkLocationAuthorization() { - switch CLLocationManager.authorizationStatus() { - case .authorizedWhenInUse: - locationManager.requestLocation() - break - case .denied: - displayAlertWithButton(dialogTitle: "Turn on Location Access For this App", dialogMessage: "Please Turn \"Location Access Permission\" On From \"Settings -> Privacy -> Location Services -> OpenWeatherApp -> While Using the App\".", buttonTitle: "Okay") - case .notDetermined: - locationManager.requestWhenInUseAuthorization() - case .restricted: - displayAlertWithButton(dialogTitle: "Restricted By User", dialogMessage: "This is possibly due to active restrictions such as parental controls being in place.", buttonTitle: "Close") - case .authorizedAlways: - locationManager.requestLocation() - default: - displayAlertWithButton(dialogTitle: "Unknown Case!", dialogMessage: "This Permission is not handled in developing time.", buttonTitle: "Okay") - } - } - - func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { - checkLocationAuthorization() - } - - func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { - currentLocation = locations.last - - if let lat = currentLocation?.coordinate.latitude { - self.latitude = lat - print("CLLocationManager - Latitude: ", self.latitude) - } - - if let lon = currentLocation?.coordinate.longitude { - self.longitude = lon - print("CLLocationManager - Longitude: ", self.longitude) - } - - callReverseGeoCoder() - } - - func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { - print(error) - displayAlertWithButton(dialogTitle: "Error in Fetching Location", dialogMessage: "Error Occured: \(error). Please Check Your Location On the Settings -> Privacy -> Location Services", buttonTitle: "Close") - } - - func displayAlertWithButton(dialogTitle title: String, dialogMessage message: String, buttonTitle name: String) { - let dialogMessage = UIAlertController(title: title, message: message, preferredStyle: .alert) - let okButtonInAlert = UIAlertAction(title: name, style: .default, handler: nil) - dialogMessage.addAction(okButtonInAlert) - self.present(dialogMessage, animated: true, completion: nil) - } - - private func callReverseGeoCoder() { - let geoCoder = CLGeocoder() - let userCurrentLocation = CLLocation(latitude: self.latitude, longitude: self.longitude ) - geoCoder.reverseGeocodeLocation(userCurrentLocation, completionHandler: { (placemarks, error) in - - if let _ = error { - return - } - - guard let placemark = placemarks?.first else { - return - } - - if let placeName = placemark.locality, let placeCountry = placemark.country { - print(placeName) - self.locationName = "\(placeName), \(placeCountry)" - } else { - print("Error in callReverseGeoCoder()") - } - }) - } -} - -// MARK:- Define fetchAPIData() -extension HomeViewController { - func fetchAPIData(completionHandler: @escaping (WeatherData) -> ()) { - let baseAddress = "https://api.openweathermap.org/data/2.5/onecall?" - let lat = "lat=\(latitude)" - let lon = "&lon=\(longitude)" - let openWeatherMapAPIKEY = "&appid=" + openWeatherMap_access_token - let excludesFromAPIresponse = "&exclude=minutely,alerts" - let unitsOfDataFromAPIResponse = "&units=metric" - - print("Latitude in fetchAPIData()", latitude) - print("Longitude in fetchAPIData()", longitude) - - let urlString = baseAddress + lat + lon + unitsOfDataFromAPIResponse + excludesFromAPIresponse + openWeatherMapAPIKEY - - print(urlString) - - guard let url = URL(string: urlString) else { - print("Error In URL construction in fetchAPIData()") - return - } - - let task = URLSession.shared.dataTask(with: url) { (data, response, error) in - guard let data = data, error == nil else { - print("Error Occured in Retrieving Data in fetchAPIData()") - return - } - - if let jsonData = try? JSONSerialization.jsonObject(with: data, options: []) { - print(jsonData) - } - - do { - let result = try JSONDecoder().decode(WeatherData.self, from: data) - completionHandler(result) - } catch { - print("Error in Data Decoding in fetchAPIData()", error) - } - } - task.resume() - } -} - -// MARK:- CollectionView -extension HomeViewController: UICollectionViewDataSource { - - func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return hourlyData.count - } - - func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionview_cell", for: indexPath) as! CustomCollectionViewCell - print("\(self.hourlyData[indexPath.row].temp)°C") - print(self.hourlyData[indexPath.row].dt.fromUnixTimeToTime()) - cell.forecastHourlyTemp.text = "\(self.hourlyData[indexPath.row].temp)°C" - cell.forecastHourlyTime.text = self.hourlyData[indexPath.row].dt.fromUnixTimeToTime() -// cell.forecastHourlyWeatherIcon.image = UIImage(named: self.HourlyData[indexPath.row].weather[0].icon) - let urlString = "https://openweathermap.org/img/wn/" + self.hourlyData[indexPath.row].weather[0].icon + ".png" - if let url = URL(string: urlString) { - cell.forecastHourlyWeatherIcon.imageLoad(from: url) - } else { - print("Error in URL() in collectionView - cellForItemAt indexPath") - } - - return cell - } - - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - return CGSize(width: view.frame.width, height: 180) - } -} - -// MARK:- Segue -extension HomeViewController { - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - let nextViewController = segue.destination as? WeeklyDataViewController - - if nextViewController != nil { -// nextViewController?.latitude = self.latitude -// nextViewController?.longitude = self.longitude - nextViewController?.nextSevenDaysData = self.nextSevenDaysData - } - } - - @IBAction func unwindToHomeViewController(_ sender: UIStoryboardSegue) { - guard let userSearchedLocationName = UserDefaults.standard.string(forKey: "userSelectedPlacesnameValue") else { - print("Error in retriving data from userDefaults") - return - } - - retriveSavedLocationData(for: userSearchedLocationName) - - DispatchQueue.main.async { - self.spinner.startAnimating() - } - - callFetchAPIData() - -// if let sourceVC = sender.source as? SearchCityNameViewController { -// // store data in the variables -// locationName = sourceVC.userSelectedPlacesname -// latitude = sourceVC.userSelectedPlacesLatitude -// longitude = sourceVC.userSelectedPlacesLongitude -// -// DispatchQueue.main.async { -// self.spinner.startAnimating() -// } -// -// // Call the FetchAPIData() -// timer.invalidate() -// callFetchAPIData() -// } - } -} diff --git a/OpenWeatherApp/CodableStructures.swift b/OpenWeatherApp/Model/CodableStructures.swift similarity index 69% rename from OpenWeatherApp/CodableStructures.swift rename to OpenWeatherApp/Model/CodableStructures.swift index 014edbc..2cc68f5 100644 --- a/OpenWeatherApp/CodableStructures.swift +++ b/OpenWeatherApp/Model/CodableStructures.swift @@ -32,17 +32,17 @@ struct WeatherData: Codable { struct Daily: Codable { var dt: Int - let sunrise: Int - let sunset: Int - let temp: Temp - let feels_like: feels_like - let pressure: Int - let humidity: Int - let dew_point: Double - let wind_speed: Double - let wind_deg: Int - let weather: [Weather] - let clouds: Int + var sunrise: Int + var sunset: Int + var temp: Temp + var feels_like: feels_like + var pressure: Int + var humidity: Int + var dew_point: Double + var wind_speed: Double + var wind_deg: Int + var weather: [Weather] + var clouds: Int // let pop: Int? let uvi: Double @@ -54,12 +54,12 @@ struct Daily: Codable { struct Temp: Codable { - let day: Double - let min: Double - let max: Double - let night: Double - let eve: Double - let morn: Double + var day: Double + var min: Double + var max: Double + var night: Double + var eve: Double + var morn: Double } @@ -74,27 +74,27 @@ struct feels_like: Codable { struct Weather: Codable { let id: Int let main: String - let description: String - let icon: String + var description: String + var icon: String } struct Current: Codable { var dt : Int - let sunrise : Int - let sunset : Int - let temp : Double - let feels_like : Double - let weather : [Weather] + var sunrise : Int + var sunset : Int + var temp : Double + var feels_like : Double + var weather : [Weather] } struct Hourly: Codable { - let dt: Int - let temp: Double - let feels_like: Double - let weather: [Weather] + var dt: Int + var temp: Double + var feels_like: Double + var weather: [Weather] } struct Response: Codable { diff --git a/OpenWeatherApp/Extensions.swift b/OpenWeatherApp/Model/Extensions.swift similarity index 100% rename from OpenWeatherApp/Extensions.swift rename to OpenWeatherApp/Model/Extensions.swift diff --git a/OpenWeatherApp/Model/RealmClasses.swift b/OpenWeatherApp/Model/RealmClasses.swift new file mode 100644 index 0000000..b5c1593 --- /dev/null +++ b/OpenWeatherApp/Model/RealmClasses.swift @@ -0,0 +1,63 @@ +// +// RealmWeatherAppData.swift +// OpenWeatherApp +// +// Created by Fahim Rahman on 24/5/21. +// + +import Foundation +import RealmSwift + +class CityNameAndLocationInfoInRealm: Object { + @objc dynamic var stored_cityName: String = "" + @objc dynamic var stored_latitude: Double = 0.0 + @objc dynamic var stored_longitude: Double = 0.0 +} + +class NextSevenDaysWeatherForecastInRealm: Object { + @objc dynamic var date_time: Int = 0 + @objc dynamic var temperature: TemperatureResponseInRealm? + var weather: List = List() +} + +class TemperatureResponseInRealm: Object { + @objc dynamic var max_temperature: Double = 0.0 + @objc dynamic var min_temperature: Double = 0.0 +} + +//class NextSevenDaysWeatherDetailsInRealm: Object { +// @objc dynamic var weather_details: WeatherDetailsInRealm? = WeatherDetailsInRealm() +//} + +class PresentDayWeatherForecastInRealm: Object { + @objc dynamic var dt: Int = 0 + @objc dynamic var sunrise : Int = 0 + @objc dynamic var sunset : Int = 0 + @objc dynamic var temp : Double = 0.0 + @objc dynamic var feels_like : Double = 0.0 + var weather: List = List() +} + +//class PresentDayWeatherDetailsInRealm: Object { +// @objc dynamic var weather_details: WeatherDetailsInRealm? = WeatherDetailsInRealm() +//} + +class PresentDayHourlyWeatherForecastInRealm: Object { + @objc dynamic var dt: Int = 0 + @objc dynamic var temp: Double = 0.0 + @objc dynamic var feels_like: Double = 0.0 + var weather: List = List() +} + +//class PresentDayHourlyWeatherDetailsInRealm: Object { +// @objc dynamic var weather_details: WeatherDetailsInRealm? = WeatherDetailsInRealm() +//} + +class PresentDayTimezoneOffsetInRealm: Object { + @objc dynamic var timezone_offset: String = "" +} + +class WeatherDetailsInRealm: Object, Codable { + @objc dynamic var weather_description: String = "" + @objc dynamic var weather_icon: String = "" +} diff --git a/OpenWeatherApp/Model/RealmDataAccessUtility.swift b/OpenWeatherApp/Model/RealmDataAccessUtility.swift new file mode 100644 index 0000000..fd62f48 --- /dev/null +++ b/OpenWeatherApp/Model/RealmDataAccessUtility.swift @@ -0,0 +1,248 @@ +// +// RealmDataAccessUtility.swift +// OpenWeatherApp +// +// Created by Fahim Rahman on 30/5/21. +// + +import Foundation +import RealmSwift + +class RealmDataAccessUtility { + static func deleteCityNameAndCoordinateClassData() { + do { + let realmReference = try Realm() + + try realmReference.write { + realmReference.delete(realmReference.objects(CityNameAndLocationInfoInRealm.self)) + } + } catch { + print("Error in delete city name and location class data in realm") + } + } + + static func deletePresentDayHourlyWeatherForecastClassData() { + do { + let realmReference = try Realm() + + try realmReference.write { + realmReference.delete(realmReference.objects(PresentDayHourlyWeatherForecastInRealm.self)) +// realmReference.delete(realmReference.objects(PresentDayHourlyWeatherDetailsInRealm.self)) + } + } catch { + print("Error in delete nextSevenDayForecast class data in realm") + } + } + + static func deletePresentDayWeatherForecastClassData() { + do { + let realmReference = try Realm() + + try realmReference.write { + realmReference.delete(realmReference.objects(PresentDayWeatherForecastInRealm.self)) +// realmReference.delete(realmReference.objects(PresentDayWeatherDetailsInRealm.self)) + } + } catch { + print("Error in delete Present day weather forecast data from realm") + } + } + + static func deleteNextSevenDaysWeatherForecastClassData() { + do { + let realmReference = try Realm() + + try realmReference.write { + realmReference.delete(realmReference.objects(NextSevenDaysWeatherForecastInRealm.self)) + realmReference.delete(realmReference.objects(TemperatureResponseInRealm.self)) +// realmReference.delete(realmReference.objects(NextSevenDaysWeatherDetailsInRealm.self)) + } + } catch { + print("Error in delete next seven days weather forecast data from realm") + } + } + + static func deletePresentDayTimezoneOffsetClassData() { + do { + let realmReference = try Realm() + + try realmReference.write { + realmReference.delete(realmReference.objects(PresentDayTimezoneOffsetInRealm.self)) + } + } catch { + print("Error in delete next seven days weather forecast data from realm") + } + } + + static func deleteWeatherDetailsClassData() { + do { + let realmReference = try Realm() + + try realmReference.write { + realmReference.delete(realmReference.objects(WeatherDetailsInRealm.self)) + } + } catch { + print("Error in delete next seven days weather forecast data from realm") + } + } + + + static func saveCityNameAndCoordinatesForLocation(name: String, latitude: Double, longitude: Double) { + do { + let realmReference = try Realm() + + RealmDataAccessUtility.deleteCityNameAndCoordinateClassData() + + let storeCityNameAndLocationInfo = CityNameAndLocationInfoInRealm() + storeCityNameAndLocationInfo.stored_cityName = name + storeCityNameAndLocationInfo.stored_latitude = latitude + storeCityNameAndLocationInfo.stored_longitude = longitude + + try realmReference.write { + realmReference.add(storeCityNameAndLocationInfo) + } + } catch { + print("Error Saving City name and Coordinates") + } + } + + static func savePresentDaysHourlyWeatherForecastFrom(data: [Hourly]) { + do { + let realmReference = try Realm() + + RealmDataAccessUtility.deletePresentDayHourlyWeatherForecastClassData() + + + for item in data { + let weatherResponseObject = List() + let saveWeatherResponse = WeatherDetailsInRealm() + saveWeatherResponse.weather_icon = item.weather[0].icon + weatherResponseObject.append(saveWeatherResponse) + + let hourlyWeatherDataObject = PresentDayHourlyWeatherForecastInRealm() + hourlyWeatherDataObject.dt = item.dt + hourlyWeatherDataObject.temp = item.temp + hourlyWeatherDataObject.feels_like = item.feels_like + hourlyWeatherDataObject.weather = weatherResponseObject + + try realmReference.write { + realmReference.add(hourlyWeatherDataObject) + } + } + } catch { + print("Error in saving Hourly data in Realm") + } + } + + static func savePresentDayWeatherForecastFrom(presentDayForecast: Current) { + do { + let realmReference = try Realm() + + RealmDataAccessUtility.deletePresentDayWeatherForecastClassData() + + let weatherResponseObject = List() + let saveWeatherResponse = WeatherDetailsInRealm() + saveWeatherResponse.weather_description = presentDayForecast.weather[0].description + saveWeatherResponse.weather_icon = presentDayForecast.weather[0].icon + weatherResponseObject.append(saveWeatherResponse) + + let currentWeatherDataObject = PresentDayWeatherForecastInRealm() + currentWeatherDataObject.dt = presentDayForecast.dt + currentWeatherDataObject.sunrise = presentDayForecast.sunrise + currentWeatherDataObject.sunset = presentDayForecast.sunset + currentWeatherDataObject.temp = presentDayForecast.temp + currentWeatherDataObject.feels_like = presentDayForecast.feels_like + currentWeatherDataObject.weather = weatherResponseObject + + try realmReference.write { + realmReference.add(currentWeatherDataObject) + } + + } catch { + print("Error in saving Current Weather Data") + } + } + + static func saveNextSevenDaysForecastFrom(data: [Daily]) { + do { + let realmReference = try Realm() + + RealmDataAccessUtility.deleteNextSevenDaysWeatherForecastClassData() + + for eachItem in data { + let weatherList = List() + let saveWeather = WeatherDetailsInRealm() + saveWeather.weather_description = eachItem.weather.first?.description ?? "" + saveWeather.weather_icon = eachItem.weather.first?.icon ?? "" + weatherList.append(saveWeather) + + let saveTemperature = TemperatureResponseInRealm() + saveTemperature.max_temperature = eachItem.temp.max + saveTemperature.min_temperature = eachItem.temp.min + + let saveDailyWeatherForecast = NextSevenDaysWeatherForecastInRealm() + saveDailyWeatherForecast.date_time = eachItem.dt + saveDailyWeatherForecast.temperature = saveTemperature + saveDailyWeatherForecast.weather = weatherList + + try realmReference.write { + realmReference.add(saveDailyWeatherForecast) + } + } + + } catch { + print("Error in saving NextSevendaysData") + } + } + + static func savePresentDayTime(from identifier: String) { + do { + let realmReference = try Realm() + + RealmDataAccessUtility.deletePresentDayTimezoneOffsetClassData() + + let saveTimezoneIdentifier = PresentDayTimezoneOffsetInRealm() + saveTimezoneIdentifier.timezone_offset = identifier + + try realmReference.write { + realmReference.add(saveTimezoneIdentifier) + } + + } catch { + print("Error in saving timezone offset") + } + } + + static func checkIfNextSevenDaysForecastPresentInRealm() -> Bool { + do { + let realmReference = try Realm() + let fetchedNextSevenDaysDataFromRealm = realmReference.objects(NextSevenDaysWeatherForecastInRealm.self) + + if fetchedNextSevenDaysDataFromRealm.count > 0 { + return true + } + } catch { + print("Error in Realm Integration in HomeViewController()") + } + return false + } + + static func checkIfWeatherForcastsPresentInRealm() -> Bool{ + do { + let realmReference = try Realm() + print(Realm.Configuration.defaultConfiguration) + + let fetchedLocationDataFromRealm = realmReference.objects(CityNameAndLocationInfoInRealm.self) + let fetchedCurrentWeatherDataFromRealm = realmReference.objects(PresentDayWeatherForecastInRealm.self) + let fetchedPresentDayHourlyDataFromRealm = realmReference.objects(PresentDayHourlyWeatherForecastInRealm.self) + + if fetchedLocationDataFromRealm.count > 0, + fetchedCurrentWeatherDataFromRealm.count > 0, + fetchedPresentDayHourlyDataFromRealm.count > 0{ + return true + } + } catch { + print("Error in Realm Integration in HomeViewController()") + } + return false + } +} diff --git a/OpenWeatherApp/AppDelegate.swift b/OpenWeatherApp/Utilities/AppDelegate.swift similarity index 98% rename from OpenWeatherApp/AppDelegate.swift rename to OpenWeatherApp/Utilities/AppDelegate.swift index ccd9a81..a71e3c3 100644 --- a/OpenWeatherApp/AppDelegate.swift +++ b/OpenWeatherApp/Utilities/AppDelegate.swift @@ -10,7 +10,7 @@ import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { - + var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. diff --git a/OpenWeatherApp/FileReader.swift b/OpenWeatherApp/Utilities/FileReader.swift similarity index 100% rename from OpenWeatherApp/FileReader.swift rename to OpenWeatherApp/Utilities/FileReader.swift diff --git a/OpenWeatherApp/Utilities/InternetConnectionCheck.swift b/OpenWeatherApp/Utilities/InternetConnectionCheck.swift new file mode 100644 index 0000000..53d01ab --- /dev/null +++ b/OpenWeatherApp/Utilities/InternetConnectionCheck.swift @@ -0,0 +1,32 @@ +// +// InternetConnectionTest.swift +// OpenWeatherApp +// +// Created by Fahim Rahman on 26/5/21. +// + +import Foundation +import SystemConfiguration + +public class InternetConnectionCheck { + static func ConnectionStatus() -> Bool { + + var zeroAddress = sockaddr_in() + zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress)) + zeroAddress.sin_family = sa_family_t(AF_INET) + + let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) { + $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in + SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress) + } + } + + var flags = SCNetworkReachabilityFlags() + if !SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) { + return false + } + let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0 + let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0 + return (isReachable && !needsConnection) + } +} diff --git a/OpenWeatherApp/SceneDelegate.swift b/OpenWeatherApp/Utilities/SceneDelegate.swift similarity index 100% rename from OpenWeatherApp/SceneDelegate.swift rename to OpenWeatherApp/Utilities/SceneDelegate.swift diff --git a/OpenWeatherApp/AutoCompleteLocationCustomTableViewCell.swift b/OpenWeatherApp/View/AutoCompleteLocationCustomTableViewCell.swift similarity index 100% rename from OpenWeatherApp/AutoCompleteLocationCustomTableViewCell.swift rename to OpenWeatherApp/View/AutoCompleteLocationCustomTableViewCell.swift diff --git a/OpenWeatherApp/CustomCollectionViewCell.swift b/OpenWeatherApp/View/CustomCollectionViewCell.swift similarity index 100% rename from OpenWeatherApp/CustomCollectionViewCell.swift rename to OpenWeatherApp/View/CustomCollectionViewCell.swift diff --git a/OpenWeatherApp/CustomTableViewCell.swift b/OpenWeatherApp/View/CustomTableViewCell.swift similarity index 100% rename from OpenWeatherApp/CustomTableViewCell.swift rename to OpenWeatherApp/View/CustomTableViewCell.swift diff --git a/OpenWeatherApp/WeeklyDataViewController.swift b/OpenWeatherApp/WeeklyDataViewController.swift deleted file mode 100644 index b9e8f12..0000000 --- a/OpenWeatherApp/WeeklyDataViewController.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// WeeklyDataViewController.swift -// OpenWeatherApp -// -// Created by Fahim Rahman on 29/3/21. -// - -import UIKit - -class WeeklyDataViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { - - @IBOutlet weak var tableView: UITableView! - -// var latitude = 0.0 -// var longitude = 0.0 - - var nextSevenDaysData = [Daily]() - - var iconImage: UIImage? = nil - - // MARK:- viewDidLoad() - override func viewDidLoad() { - super.viewDidLoad() - - tableView.dataSource = self - tableView.delegate = self - - tableView.backgroundView = UIImageView(image: UIImage(named: "background.jpeg")) - } - - - // MARK:- TableView Part - func numberOfSections(in tableView: UITableView) -> Int { - return 1 - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return nextSevenDaysData.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - - let cell = tableView.dequeueReusableCell(withIdentifier: "table_cell") as! CustomTableViewCell - - if indexPath.row <= 6 { - cell.forecastDate.text = "\(self.nextSevenDaysData[indexPath.row].dt.fromUnixTimeToDate())" -// cell.forecastSunriseTime.text = "Sunrise: " + self.NextSevenDaysData[indexPath.row].sunrise.fromUnixTimeToTime() -// cell.forecastSunsetTime.text = "Sunset: " + self.NextSevenDaysData[indexPath.row].sunset.fromUnixTimeToTime() -// cell.forecastWeatherIcon.image = UIImage(named: self.NextSevenDaysData[indexPath.row].weather[0].icon) - let url = URL(string: "https://openweathermap.org/img/wn/" + self.nextSevenDaysData[indexPath.row].weather[0].icon + ".png") - cell.forecastWeatherIcon.imageLoad(from: url!) - cell.forecastWeatherDescription.text = "" + self.nextSevenDaysData[indexPath.row].weather[0].description.capitalized - cell.forecastMaxTemp.text = "Max: \(self.nextSevenDaysData[indexPath.row].temp.max)°C" - cell.forecastMinTemp.text = "Min: \(self.nextSevenDaysData[indexPath.row].temp.min)°C" - } -// cell.backgroundColor = indexPath.row % 2 == 0 ? .cyan : .lightGray - - return cell - } - - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return 100 - } -} diff --git a/Podfile b/Podfile new file mode 100644 index 0000000..489fe8c --- /dev/null +++ b/Podfile @@ -0,0 +1,11 @@ +# Uncomment the next line to define a global platform for your project +# platform :ios, '9.0' +use_frameworks! + +target 'OpenWeatherApp' do + # Pods for OpenWeatherApp + pod 'RealmSwift' + pod 'Alamofire', '~> 5.4' + + +end diff --git a/Podfile.lock b/Podfile.lock new file mode 100644 index 0000000..b817b70 --- /dev/null +++ b/Podfile.lock @@ -0,0 +1,26 @@ +PODS: + - Alamofire (5.4.3) + - Realm (10.7.6): + - Realm/Headers (= 10.7.6) + - Realm/Headers (10.7.6) + - RealmSwift (10.7.6): + - Realm (= 10.7.6) + +DEPENDENCIES: + - Alamofire (~> 5.4) + - RealmSwift + +SPEC REPOS: + trunk: + - Alamofire + - Realm + - RealmSwift + +SPEC CHECKSUMS: + Alamofire: e447a2774a40c996748296fa2c55112fdbbc42f9 + Realm: ed860452717c8db8f4bf832b6807f7f2ce708839 + RealmSwift: e31c4ddbcc42ac879313d656b86f9ca539f6f4f4 + +PODFILE CHECKSUM: 2f0c9193fc7e66c27cf901c752b68394f4a23f09 + +COCOAPODS: 1.10.1