diff --git a/Whatsapp-Swift/Whatsapp-Swift.xcodeproj/project.pbxproj b/Whatsapp-Swift/Whatsapp-Swift.xcodeproj/project.pbxproj new file mode 100644 index 0000000..e295372 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift.xcodeproj/project.pbxproj @@ -0,0 +1,627 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 550D42651C765206006A6961 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550D42641C765206006A6961 /* AppDelegate.swift */; }; + 550D426A1C765206006A6961 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 550D42681C765206006A6961 /* Main.storyboard */; }; + 550D427A1C765206006A6961 /* Whatsapp_SwiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550D42791C765206006A6961 /* Whatsapp_SwiftTests.swift */; }; + 550D42851C765206006A6961 /* Whatsapp_SwiftUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550D42841C765206006A6961 /* Whatsapp_SwiftUITests.swift */; }; + 550D429C1C765514006A6961 /* Chat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550D429A1C765514006A6961 /* Chat.swift */; }; + 550D429D1C765514006A6961 /* ChatController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550D429B1C765514006A6961 /* ChatController.swift */; }; + 550D42A01C7658BC006A6961 /* MessageController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550D429F1C7658BC006A6961 /* MessageController.swift */; }; + 550D42A21C765904006A6961 /* Contact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550D42A11C765904006A6961 /* Contact.swift */; }; + 550D42A41C76590C006A6961 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550D42A31C76590C006A6961 /* Message.swift */; }; + 550D42A61C765FB3006A6961 /* LocalStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550D42A51C765FB3006A6961 /* LocalStorage.swift */; }; + 550D42A91C7664D1006A6961 /* ChatCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550D42A71C7664D1006A6961 /* ChatCell.swift */; }; + 550D42AA1C7664D1006A6961 /* MessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550D42A81C7664D1006A6961 /* MessageCell.swift */; }; + 550D42AD1C7673F0006A6961 /* Inputbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550D42AB1C7673F0006A6961 /* Inputbar.swift */; }; + 550D42AE1C7673F0006A6961 /* TableArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550D42AC1C7673F0006A6961 /* TableArray.swift */; }; + 550D42B61C767CB7006A6961 /* DAKeyboardControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 550D42B11C767CB7006A6961 /* DAKeyboardControl.m */; }; + 550D42B71C767CB7006A6961 /* HPGrowingTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 550D42B31C767CB7006A6961 /* HPGrowingTextView.m */; }; + 550D42B81C767CB7006A6961 /* HPTextViewInternal.m in Sources */ = {isa = PBXBuildFile; fileRef = 550D42B51C767CB7006A6961 /* HPTextViewInternal.m */; }; + 550D42BA1C768959006A6961 /* MessageGateway.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550D42B91C768959006A6961 /* MessageGateway.swift */; }; + 550D42BE1C76A4BA006A6961 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 550D42BD1C76A4BA006A6961 /* Images.xcassets */; }; + 550D42C01C76A841006A6961 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 550D42BF1C76A841006A6961 /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 550D42761C765206006A6961 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 550D42591C765206006A6961 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 550D42601C765206006A6961; + remoteInfo = "Whatsapp-Swift"; + }; + 550D42811C765206006A6961 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 550D42591C765206006A6961 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 550D42601C765206006A6961; + remoteInfo = "Whatsapp-Swift"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 550D42611C765206006A6961 /* Whatsapp-Swift.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Whatsapp-Swift.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 550D42641C765206006A6961 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 550D42691C765206006A6961 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 550D42701C765206006A6961 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 550D42751C765206006A6961 /* Whatsapp-SwiftTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Whatsapp-SwiftTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 550D42791C765206006A6961 /* Whatsapp_SwiftTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Whatsapp_SwiftTests.swift; sourceTree = ""; }; + 550D427B1C765206006A6961 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 550D42801C765206006A6961 /* Whatsapp-SwiftUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Whatsapp-SwiftUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 550D42841C765206006A6961 /* Whatsapp_SwiftUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Whatsapp_SwiftUITests.swift; sourceTree = ""; }; + 550D42861C765206006A6961 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 550D429A1C765514006A6961 /* Chat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Chat.swift; sourceTree = ""; }; + 550D429B1C765514006A6961 /* ChatController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatController.swift; sourceTree = ""; }; + 550D429F1C7658BC006A6961 /* MessageController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageController.swift; sourceTree = ""; }; + 550D42A11C765904006A6961 /* Contact.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Contact.swift; sourceTree = ""; }; + 550D42A31C76590C006A6961 /* Message.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = ""; }; + 550D42A51C765FB3006A6961 /* LocalStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalStorage.swift; sourceTree = ""; }; + 550D42A71C7664D1006A6961 /* ChatCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatCell.swift; sourceTree = ""; }; + 550D42A81C7664D1006A6961 /* MessageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCell.swift; sourceTree = ""; }; + 550D42AB1C7673F0006A6961 /* Inputbar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Inputbar.swift; sourceTree = ""; }; + 550D42AC1C7673F0006A6961 /* TableArray.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableArray.swift; sourceTree = ""; }; + 550D42AF1C767CB7006A6961 /* Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Bridging-Header.h"; sourceTree = ""; }; + 550D42B01C767CB7006A6961 /* DAKeyboardControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DAKeyboardControl.h; sourceTree = ""; }; + 550D42B11C767CB7006A6961 /* DAKeyboardControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DAKeyboardControl.m; sourceTree = ""; }; + 550D42B21C767CB7006A6961 /* HPGrowingTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HPGrowingTextView.h; sourceTree = ""; }; + 550D42B31C767CB7006A6961 /* HPGrowingTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HPGrowingTextView.m; sourceTree = ""; }; + 550D42B41C767CB7006A6961 /* HPTextViewInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HPTextViewInternal.h; sourceTree = ""; }; + 550D42B51C767CB7006A6961 /* HPTextViewInternal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HPTextViewInternal.m; sourceTree = ""; }; + 550D42B91C768959006A6961 /* MessageGateway.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageGateway.swift; sourceTree = ""; }; + 550D42BD1C76A4BA006A6961 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + 550D42BF1C76A841006A6961 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 550D425E1C765206006A6961 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 550D42721C765206006A6961 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 550D427D1C765206006A6961 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 550D42581C765206006A6961 = { + isa = PBXGroup; + children = ( + 550D42631C765206006A6961 /* Whatsapp-Swift */, + 550D42781C765206006A6961 /* Whatsapp-SwiftTests */, + 550D42831C765206006A6961 /* Whatsapp-SwiftUITests */, + 550D42621C765206006A6961 /* Products */, + ); + sourceTree = ""; + }; + 550D42621C765206006A6961 /* Products */ = { + isa = PBXGroup; + children = ( + 550D42611C765206006A6961 /* Whatsapp-Swift.app */, + 550D42751C765206006A6961 /* Whatsapp-SwiftTests.xctest */, + 550D42801C765206006A6961 /* Whatsapp-SwiftUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 550D42631C765206006A6961 /* Whatsapp-Swift */ = { + isa = PBXGroup; + children = ( + 550D42921C765429006A6961 /* Controllers */, + 550D42931C765433006A6961 /* Cell */, + 550D42941C76543A006A6961 /* Helpers */, + 550D42951C765451006A6961 /* Model */, + 550D42961C765457006A6961 /* Gateways */, + 550D42971C765461006A6961 /* Third-Party */, + 550D42991C76547F006A6961 /* Supporting Files */, + 550D42641C765206006A6961 /* AppDelegate.swift */, + 550D42AF1C767CB7006A6961 /* Bridging-Header.h */, + 550D42681C765206006A6961 /* Main.storyboard */, + 550D42BF1C76A841006A6961 /* LaunchScreen.storyboard */, + 550D42BD1C76A4BA006A6961 /* Images.xcassets */, + ); + path = "Whatsapp-Swift"; + sourceTree = ""; + }; + 550D42781C765206006A6961 /* Whatsapp-SwiftTests */ = { + isa = PBXGroup; + children = ( + 550D42791C765206006A6961 /* Whatsapp_SwiftTests.swift */, + 550D427B1C765206006A6961 /* Info.plist */, + ); + path = "Whatsapp-SwiftTests"; + sourceTree = ""; + }; + 550D42831C765206006A6961 /* Whatsapp-SwiftUITests */ = { + isa = PBXGroup; + children = ( + 550D42841C765206006A6961 /* Whatsapp_SwiftUITests.swift */, + 550D42861C765206006A6961 /* Info.plist */, + ); + path = "Whatsapp-SwiftUITests"; + sourceTree = ""; + }; + 550D42921C765429006A6961 /* Controllers */ = { + isa = PBXGroup; + children = ( + 550D429F1C7658BC006A6961 /* MessageController.swift */, + 550D429B1C765514006A6961 /* ChatController.swift */, + ); + name = Controllers; + sourceTree = ""; + }; + 550D42931C765433006A6961 /* Cell */ = { + isa = PBXGroup; + children = ( + 550D42A71C7664D1006A6961 /* ChatCell.swift */, + 550D42A81C7664D1006A6961 /* MessageCell.swift */, + ); + name = Cell; + sourceTree = ""; + }; + 550D42941C76543A006A6961 /* Helpers */ = { + isa = PBXGroup; + children = ( + 550D42AB1C7673F0006A6961 /* Inputbar.swift */, + 550D42AC1C7673F0006A6961 /* TableArray.swift */, + 550D42A51C765FB3006A6961 /* LocalStorage.swift */, + ); + name = Helpers; + sourceTree = ""; + }; + 550D42951C765451006A6961 /* Model */ = { + isa = PBXGroup; + children = ( + 550D42A31C76590C006A6961 /* Message.swift */, + 550D42A11C765904006A6961 /* Contact.swift */, + 550D429A1C765514006A6961 /* Chat.swift */, + ); + name = Model; + sourceTree = ""; + }; + 550D42961C765457006A6961 /* Gateways */ = { + isa = PBXGroup; + children = ( + 550D42B91C768959006A6961 /* MessageGateway.swift */, + ); + name = Gateways; + sourceTree = ""; + }; + 550D42971C765461006A6961 /* Third-Party */ = { + isa = PBXGroup; + children = ( + 550D42B01C767CB7006A6961 /* DAKeyboardControl.h */, + 550D42B11C767CB7006A6961 /* DAKeyboardControl.m */, + 550D42B21C767CB7006A6961 /* HPGrowingTextView.h */, + 550D42B31C767CB7006A6961 /* HPGrowingTextView.m */, + 550D42B41C767CB7006A6961 /* HPTextViewInternal.h */, + 550D42B51C767CB7006A6961 /* HPTextViewInternal.m */, + ); + name = "Third-Party"; + sourceTree = ""; + }; + 550D42991C76547F006A6961 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 550D42701C765206006A6961 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 550D42601C765206006A6961 /* Whatsapp-Swift */ = { + isa = PBXNativeTarget; + buildConfigurationList = 550D42891C765206006A6961 /* Build configuration list for PBXNativeTarget "Whatsapp-Swift" */; + buildPhases = ( + 550D425D1C765206006A6961 /* Sources */, + 550D425E1C765206006A6961 /* Frameworks */, + 550D425F1C765206006A6961 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Whatsapp-Swift"; + productName = "Whatsapp-Swift"; + productReference = 550D42611C765206006A6961 /* Whatsapp-Swift.app */; + productType = "com.apple.product-type.application"; + }; + 550D42741C765206006A6961 /* Whatsapp-SwiftTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 550D428C1C765206006A6961 /* Build configuration list for PBXNativeTarget "Whatsapp-SwiftTests" */; + buildPhases = ( + 550D42711C765206006A6961 /* Sources */, + 550D42721C765206006A6961 /* Frameworks */, + 550D42731C765206006A6961 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 550D42771C765206006A6961 /* PBXTargetDependency */, + ); + name = "Whatsapp-SwiftTests"; + productName = "Whatsapp-SwiftTests"; + productReference = 550D42751C765206006A6961 /* Whatsapp-SwiftTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 550D427F1C765206006A6961 /* Whatsapp-SwiftUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 550D428F1C765206006A6961 /* Build configuration list for PBXNativeTarget "Whatsapp-SwiftUITests" */; + buildPhases = ( + 550D427C1C765206006A6961 /* Sources */, + 550D427D1C765206006A6961 /* Frameworks */, + 550D427E1C765206006A6961 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 550D42821C765206006A6961 /* PBXTargetDependency */, + ); + name = "Whatsapp-SwiftUITests"; + productName = "Whatsapp-SwiftUITests"; + productReference = 550D42801C765206006A6961 /* Whatsapp-SwiftUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 550D42591C765206006A6961 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0720; + LastUpgradeCheck = 0720; + ORGANIZATIONNAME = "Breno Lima"; + TargetAttributes = { + 550D42601C765206006A6961 = { + CreatedOnToolsVersion = 7.2.1; + }; + 550D42741C765206006A6961 = { + CreatedOnToolsVersion = 7.2.1; + TestTargetID = 550D42601C765206006A6961; + }; + 550D427F1C765206006A6961 = { + CreatedOnToolsVersion = 7.2.1; + TestTargetID = 550D42601C765206006A6961; + }; + }; + }; + buildConfigurationList = 550D425C1C765206006A6961 /* Build configuration list for PBXProject "Whatsapp-Swift" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 550D42581C765206006A6961; + productRefGroup = 550D42621C765206006A6961 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 550D42601C765206006A6961 /* Whatsapp-Swift */, + 550D42741C765206006A6961 /* Whatsapp-SwiftTests */, + 550D427F1C765206006A6961 /* Whatsapp-SwiftUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 550D425F1C765206006A6961 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 550D42BE1C76A4BA006A6961 /* Images.xcassets in Resources */, + 550D42C01C76A841006A6961 /* LaunchScreen.storyboard in Resources */, + 550D426A1C765206006A6961 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 550D42731C765206006A6961 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 550D427E1C765206006A6961 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 550D425D1C765206006A6961 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 550D42651C765206006A6961 /* AppDelegate.swift in Sources */, + 550D42B61C767CB7006A6961 /* DAKeyboardControl.m in Sources */, + 550D42A91C7664D1006A6961 /* ChatCell.swift in Sources */, + 550D429C1C765514006A6961 /* Chat.swift in Sources */, + 550D42A21C765904006A6961 /* Contact.swift in Sources */, + 550D42B71C767CB7006A6961 /* HPGrowingTextView.m in Sources */, + 550D42B81C767CB7006A6961 /* HPTextViewInternal.m in Sources */, + 550D42A41C76590C006A6961 /* Message.swift in Sources */, + 550D429D1C765514006A6961 /* ChatController.swift in Sources */, + 550D42AE1C7673F0006A6961 /* TableArray.swift in Sources */, + 550D42A61C765FB3006A6961 /* LocalStorage.swift in Sources */, + 550D42A01C7658BC006A6961 /* MessageController.swift in Sources */, + 550D42AD1C7673F0006A6961 /* Inputbar.swift in Sources */, + 550D42AA1C7664D1006A6961 /* MessageCell.swift in Sources */, + 550D42BA1C768959006A6961 /* MessageGateway.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 550D42711C765206006A6961 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 550D427A1C765206006A6961 /* Whatsapp_SwiftTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 550D427C1C765206006A6961 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 550D42851C765206006A6961 /* Whatsapp_SwiftUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 550D42771C765206006A6961 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 550D42601C765206006A6961 /* Whatsapp-Swift */; + targetProxy = 550D42761C765206006A6961 /* PBXContainerItemProxy */; + }; + 550D42821C765206006A6961 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 550D42601C765206006A6961 /* Whatsapp-Swift */; + targetProxy = 550D42811C765206006A6961 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 550D42681C765206006A6961 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 550D42691C765206006A6961 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 550D42871C765206006A6961 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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 = 9.2; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 550D42881C765206006A6961 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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 = 9.2; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 550D428A1C765206006A6961 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "Brand Assets"; + CLANG_ENABLE_MODULES = YES; + INFOPLIST_FILE = "Whatsapp-Swift/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.hummingbird.Whatsapp-Swift"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Whatsapp-Swift/Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 550D428B1C765206006A6961 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "Brand Assets"; + CLANG_ENABLE_MODULES = YES; + INFOPLIST_FILE = "Whatsapp-Swift/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.hummingbird.Whatsapp-Swift"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Whatsapp-Swift/Bridging-Header.h"; + }; + name = Release; + }; + 550D428D1C765206006A6961 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + INFOPLIST_FILE = "Whatsapp-SwiftTests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.hummingbird.Whatsapp-SwiftTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Whatsapp-Swift.app/Whatsapp-Swift"; + }; + name = Debug; + }; + 550D428E1C765206006A6961 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + INFOPLIST_FILE = "Whatsapp-SwiftTests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.hummingbird.Whatsapp-SwiftTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Whatsapp-Swift.app/Whatsapp-Swift"; + }; + name = Release; + }; + 550D42901C765206006A6961 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = "Whatsapp-SwiftUITests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.hummingbird.Whatsapp-SwiftUITests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_TARGET_NAME = "Whatsapp-Swift"; + USES_XCTRUNNER = YES; + }; + name = Debug; + }; + 550D42911C765206006A6961 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = "Whatsapp-SwiftUITests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.hummingbird.Whatsapp-SwiftUITests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_TARGET_NAME = "Whatsapp-Swift"; + USES_XCTRUNNER = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 550D425C1C765206006A6961 /* Build configuration list for PBXProject "Whatsapp-Swift" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 550D42871C765206006A6961 /* Debug */, + 550D42881C765206006A6961 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 550D42891C765206006A6961 /* Build configuration list for PBXNativeTarget "Whatsapp-Swift" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 550D428A1C765206006A6961 /* Debug */, + 550D428B1C765206006A6961 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 550D428C1C765206006A6961 /* Build configuration list for PBXNativeTarget "Whatsapp-SwiftTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 550D428D1C765206006A6961 /* Debug */, + 550D428E1C765206006A6961 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 550D428F1C765206006A6961 /* Build configuration list for PBXNativeTarget "Whatsapp-SwiftUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 550D42901C765206006A6961 /* Debug */, + 550D42911C765206006A6961 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 550D42591C765206006A6961 /* Project object */; +} diff --git a/Whatsapp-Swift/Whatsapp-Swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Whatsapp-Swift/Whatsapp-Swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..6858205 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Whatsapp-Swift/Whatsapp-Swift/AppDelegate.swift b/Whatsapp-Swift/Whatsapp-Swift/AppDelegate.swift new file mode 100644 index 0000000..b5ee153 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/AppDelegate.swift @@ -0,0 +1,38 @@ +// +// AppDelegate.swift +// Whatsapp-Swift +// +// Created by Breno Lima on 2/18/16. +// Copyright © 2016 Breno Lima. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + + func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { + return true + } + + func applicationWillResignActive(application: UIApplication) { + } + + func applicationDidEnterBackground(application: UIApplication) { + } + + func applicationWillEnterForeground(application: UIApplication) { + } + + func applicationDidBecomeActive(application: UIApplication) { + } + + func applicationWillTerminate(application: UIApplication) { + } + + +} + diff --git a/Whatsapp-Swift/Whatsapp-Swift/Base.lproj/Main.storyboard b/Whatsapp-Swift/Whatsapp-Swift/Base.lproj/Main.storyboard new file mode 100644 index 0000000..e296e73 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/Base.lproj/Main.storyboard @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Whatsapp-Swift/Whatsapp-Swift/Bridging-Header.h b/Whatsapp-Swift/Whatsapp-Swift/Bridging-Header.h new file mode 100644 index 0000000..c8c1ab8 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/Bridging-Header.h @@ -0,0 +1,3 @@ +#import "DAKeyboardControl.h" +#import "HPGrowingTextView.h" +#import "HPTextViewInternal.h" \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/Chat.swift b/Whatsapp-Swift/Whatsapp-Swift/Chat.swift new file mode 100644 index 0000000..f97e0f3 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/Chat.swift @@ -0,0 +1,35 @@ +// +// Chat.swift +// Whatsapp +// +// Created by Rafael Castro on 7/24/15. / Swift Version by Breno Oliveira on 02/18/16. +// Copyright (c) 2015 HummingBird. All rights reserved. +// + +import UIKit +import Foundation + +class Chat { + var contact:Contact! + var numberOfUnreadMessages:Int = 0 + + var lastMessage:Message! { + willSet (val) { + if self.lastMessage != nil { + if self.lastMessage.date.earlierDate(val.date) != self.lastMessage.date { + self.lastMessage = val + } + } + } + } + + var identifier:String { + get { + return self.contact.identifier + } + } + + func save() { + LocalStorage.sharedInstance.storeChat(self) + } +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/ChatCell.swift b/Whatsapp-Swift/Whatsapp-Swift/ChatCell.swift new file mode 100644 index 0000000..92be9df --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/ChatCell.swift @@ -0,0 +1,59 @@ +// +// ChatListCell.swift +// Whatsapp +// +// Created by Rafael Castro on 7/24/15. / Swift Version by Breno Oliveira on 02/18/16. +// Copyright (c) 2015 HummingBird. All rights reserved. +// + +import UIKit + +class ChatCell:UITableViewCell { + @IBOutlet weak var nameLabel:UILabel! + @IBOutlet weak var messageLabel:UILabel! + @IBOutlet weak var timeLabel:UILabel! + @IBOutlet weak var picture:UIImageView! + @IBOutlet weak var notificationLabel:UILabel! + + override var imageView:UIImageView { + set { + + } + get { + return self.picture + } + } + + var chat:Chat! { + didSet { + self.nameLabel.text = chat.contact.name + self.messageLabel.text = chat.lastMessage.text + self.updateTimeLabelWithDate(chat.lastMessage.date) + self.updateUnreadMessagesIcon(chat.numberOfUnreadMessages) + } + } + + override func awakeFromNib () { + self.picture.layer.cornerRadius = self.picture.frame.size.width/2 + self.picture.layer.masksToBounds = true + self.notificationLabel.layer.cornerRadius = self.notificationLabel.frame.size.width/2 + self.notificationLabel.layer.masksToBounds = true + self.nameLabel.text = "" + self.messageLabel.text = "" + self.timeLabel.text = "" + } + + func updateTimeLabelWithDate(date:NSDate) { + let df = NSDateFormatter() + df.timeStyle = .ShortStyle + df.dateStyle = .NoStyle + df.doesRelativeDateFormatting = false + self.timeLabel.text = df.stringFromDate(date) + } + + func updateUnreadMessagesIcon(numberOfUnreadMessages:Int) + { + self.notificationLabel.hidden = numberOfUnreadMessages == 0 + self.notificationLabel.text = String(numberOfUnreadMessages) + } +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/ChatController.swift b/Whatsapp-Swift/Whatsapp-Swift/ChatController.swift new file mode 100644 index 0000000..7b1717a --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/ChatController.swift @@ -0,0 +1,91 @@ +// +// ChatListController.swift +// Whatsapp +// +// Created by Rafael Castro on 7/24/15. / Swift Version by Breno Oliveira on 02/18/16. +// Copyright (c) 2015 HummingBird. All rights reserved. +// + +import UIKit + +class ChatController: UIViewController, UITableViewDataSource, UITableViewDelegate { + + @IBOutlet weak var tableView:UITableView! + var tableData:[Chat] = [] + + override func viewDidLoad() { + super.viewDidLoad() + self.setTableView() + self.setTest() + + self.title = "Chats" + } + + override func viewWillAppear(animated:Bool) { + super.viewWillAppear(animated) + self.tableView.reloadData() + } + + func setTableView() { + self.tableView.delegate = self + self.tableView.dataSource = self + self.tableView.tableFooterView = UIView(frame:CGRectMake(0, 0,self.view.frame.size.width, 10)) + self.tableView.backgroundColor = UIColor.clearColor() + } + + func setTest() { + let contact = Contact() + contact.name = "Player 1" + contact.identifier = "12345" + + let chat = Chat() + chat.contact = contact + + let texts = ["Hello!", + "This project try to implement a chat UI similar to Whatsapp app.", + "Is it close enough?"] + + var lastMessage:Message! + for text in texts { + let message = Message() + message.text = text + message.sender = .Someone + message.status = .Received + message.chatId = chat.identifier + + LocalStorage.sharedInstance.storeMessage(message) + lastMessage = message + } + + chat.numberOfUnreadMessages = texts.count + chat.lastMessage = lastMessage + + self.tableData.append(chat) + } + + // MARK - TableViewDataSource + + func tableView(tableView:UITableView, numberOfRowsInSection section:NSInteger) -> Int + { + return self.tableData.count + } + + func tableView(tableView:UITableView, cellForRowAtIndexPath indexPath:NSIndexPath) -> UITableViewCell + { + let cellIdentifier = "ChatListCell" + let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! ChatCell + cell.chat = self.tableData[indexPath.row] + + return cell + } + + // MARK - UITableViewDelegate + + func tableView(tableView:UITableView, didSelectRowAtIndexPath indexPath:NSIndexPath) { + self.tableView.deselectRowAtIndexPath(indexPath, animated:true) + + let controller = self.storyboard!.instantiateViewControllerWithIdentifier("Message") as! MessageController + controller.chat = self.tableData[indexPath.row] + self.navigationController!.pushViewController(controller, animated:true) + } +} diff --git a/Whatsapp-Swift/Whatsapp-Swift/Contact.swift b/Whatsapp-Swift/Whatsapp-Swift/Contact.swift new file mode 100644 index 0000000..47455f1 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/Contact.swift @@ -0,0 +1,31 @@ +// +// Contact.swift +// Whatsapp +// +// Created by Magneto on 2/12/16. / Swift Version by Breno Oliveira on 02/18/16. +// Copyright © 2016 HummingBird. All rights reserved. +// + +import UIKit + +class Contact { + var identifier:String = "" + var name:String = "" + var imageId:String = "" + + func contactFromDictionary(dict:NSDictionary) -> Contact { + let contact = Contact() + contact.name = dict["name"] as! String + contact.identifier = dict["id"] as! String + contact.imageId = dict["imageId"] as! String + return contact + } + + func hasImage() -> Bool { + return self.imageId.characters.count > 0 + } + + func save() { + LocalStorage.sharedInstance.storeContact(self) + } +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/DAKeyboardControl.h b/Whatsapp-Swift/Whatsapp-Swift/DAKeyboardControl.h new file mode 100755 index 0000000..bbc1ef5 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/DAKeyboardControl.h @@ -0,0 +1,60 @@ +// +// DAKeyboardControl.h +// DAKeyboardControlExample +// +// Created by Daniel Amitay on 7/14/12. +// Copyright (c) 2012 Daniel Amitay. All rights reserved. +// + +#import + +typedef void (^DAKeyboardDidMoveBlock)(CGRect keyboardFrameInView, BOOL opening, BOOL closing); + +/** DAKeyboardControl allows you to easily add keyboard awareness and scrolling + dismissal (a receding keyboard ala iMessages app) to any UIView, UIScrollView + or UITableView with only 1 line of code. DAKeyboardControl automatically + extends UIView and provides a block callback with the keyboard's current origin. + */ + +@interface UIView (DAKeyboardControl) + +/** The keyboardTriggerOffset property allows you to choose at what point the + user's finger "engages" the keyboard. + */ +@property (nonatomic) CGFloat keyboardTriggerOffset; +@property (nonatomic, readonly) BOOL keyboardWillRecede; + +/** Adding pan-to-dismiss (functionality introduced in iMessages) + @param didMoveBlock called everytime the keyboard is moved so you can update + the frames of your views + @see addKeyboardNonpanningWithActionHandler: + @see removeKeyboardControl + */ +- (void)addKeyboardPanningWithActionHandler:(DAKeyboardDidMoveBlock)didMoveBlock; +- (void)addKeyboardPanningWithFrameBasedActionHandler:(DAKeyboardDidMoveBlock)didMoveFrameBasesBlock + constraintBasedActionHandler:(DAKeyboardDidMoveBlock)didMoveConstraintBasesBlock; + +/** Adding keyboard awareness (appearance and disappearance only) + @param didMoveBlock called everytime the keyboard is moved so you can update + the frames of your views + @see addKeyboardPanningWithActionHandler: + @see removeKeyboardControl + */ +- (void)addKeyboardNonpanningWithActionHandler:(DAKeyboardDidMoveBlock)didMoveBlock; +- (void)addKeyboardNonpanningWithFrameBasedActionHandler:(DAKeyboardDidMoveBlock)didMoveFrameBasesBlock + constraintBasedActionHandler:(DAKeyboardDidMoveBlock)didMoveConstraintBasesBlock; + +/** Remove the keyboard action handler + @note You MUST call this method to remove the keyboard handler before the view + goes out of memory. + */ +- (void)removeKeyboardControl; + +/** Returns the keyboard frame in the view */ +- (CGRect)keyboardFrameInView; +@property (nonatomic, readonly, getter = isKeyboardOpened) BOOL keyboardOpened; + +/** Convenience method to dismiss the keyboard */ +- (void)hideKeyboard; + +@end diff --git a/Whatsapp-Swift/Whatsapp-Swift/DAKeyboardControl.m b/Whatsapp-Swift/Whatsapp-Swift/DAKeyboardControl.m new file mode 100755 index 0000000..c629ca4 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/DAKeyboardControl.m @@ -0,0 +1,750 @@ +// +// DAKeyboardControl.m +// DAKeyboardControlExample +// +// Created by Daniel Amitay on 7/14/12. +// Copyright (c) 2012 Daniel Amitay. All rights reserved. +// + +#import "DAKeyboardControl.h" +#import + + +static inline UIViewAnimationOptions AnimationOptionsForCurve(UIViewAnimationCurve curve) +{ + return curve << 16; +} + +static char UIViewKeyboardTriggerOffset; +static char UIViewKeyboardDidMoveFrameBasedBlock; +static char UIViewKeyboardDidMoveConstraintBasedBlock; +static char UIViewKeyboardActiveInput; +static char UIViewKeyboardActiveView; +static char UIViewKeyboardPanRecognizer; +static char UIViewPreviousKeyboardRect; +static char UIViewIsPanning; +static char UIViewKeyboardOpened; + +@interface UIView (DAKeyboardControl_Internal) + +@property (nonatomic) DAKeyboardDidMoveBlock frameBasedKeyboardDidMoveBlock; +@property (nonatomic) DAKeyboardDidMoveBlock constraintBasedKeyboardDidMoveBlock; +@property (nonatomic, strong) UIResponder *keyboardActiveInput; +@property (nonatomic, strong) UIView *keyboardActiveView; +@property (nonatomic, strong) UIPanGestureRecognizer *keyboardPanRecognizer; +@property (nonatomic) CGRect previousKeyboardRect; +@property (nonatomic, getter = isPanning) BOOL panning; +@property (nonatomic, getter = isKeyboardOpened) BOOL keyboardOpened; +@end + +@implementation UIView (DAKeyboardControl) +@dynamic keyboardTriggerOffset; + ++ (void)load +{ + // Swizzle the 'addSubview:' method to ensure that all input fields + // have a valid inputAccessoryView upon addition to the view heirarchy + SEL originalSelector = @selector(addSubview:); + SEL swizzledSelector = @selector(swizzled_addSubview:); + Method originalMethod = class_getInstanceMethod(self, originalSelector); + Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector); + class_addMethod(self, + originalSelector, + class_getMethodImplementation(self, originalSelector), + method_getTypeEncoding(originalMethod)); + class_addMethod(self, + swizzledSelector, + class_getMethodImplementation(self, swizzledSelector), + method_getTypeEncoding(swizzledMethod)); + method_exchangeImplementations(originalMethod, swizzledMethod); +} + +#pragma mark - Public Methods + +- (void)addKeyboardPanningWithActionHandler:(DAKeyboardDidMoveBlock)actionHandler +{ + [self addKeyboardControl:YES frameBasedActionHandler:actionHandler constraintBasedActionHandler:0]; +} + +- (void)addKeyboardPanningWithFrameBasedActionHandler:(DAKeyboardDidMoveBlock)didMoveFrameBasesBlock constraintBasedActionHandler:(DAKeyboardDidMoveBlock)didMoveConstraintBasesBlock +{ + [self addKeyboardControl:YES frameBasedActionHandler:didMoveFrameBasesBlock constraintBasedActionHandler:didMoveConstraintBasesBlock]; +} + +- (void)addKeyboardNonpanningWithActionHandler:(DAKeyboardDidMoveBlock)actionHandler +{ + [self addKeyboardControl:NO frameBasedActionHandler:actionHandler constraintBasedActionHandler:0]; +} + +- (void)addKeyboardNonpanningWithFrameBasedActionHandler:(DAKeyboardDidMoveBlock)didMoveFrameBasesBlock + constraintBasedActionHandler:(DAKeyboardDidMoveBlock)didMoveConstraintBasesBlock +{ + [self addKeyboardControl:NO frameBasedActionHandler:didMoveFrameBasesBlock constraintBasedActionHandler:didMoveConstraintBasesBlock]; +} + +- (void)addKeyboardControl:(BOOL)panning frameBasedActionHandler:(DAKeyboardDidMoveBlock)frameBasedActionHandler constraintBasedActionHandler:(DAKeyboardDidMoveBlock)constraintBasedActionHandler +{ +#if (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_7_0) + if (panning && [self respondsToSelector:@selector(setKeyboardDismissMode:)]) { + [(UIScrollView *)self setKeyboardDismissMode:UIScrollViewKeyboardDismissModeInteractive]; + } else { + self.panning = panning; + } +#else + self.panning = panning; +#endif + self.frameBasedKeyboardDidMoveBlock = frameBasedActionHandler; + self.constraintBasedKeyboardDidMoveBlock = constraintBasedActionHandler; + + // Register for text input notifications + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(responderDidBecomeActive:) + name:UITextFieldTextDidBeginEditingNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(responderDidBecomeActive:) + name:UITextViewTextDidBeginEditingNotification + object:nil]; + + // Register for keyboard notifications + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(inputKeyboardWillShow:) + name:UIKeyboardWillShowNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(inputKeyboardDidShow) + name:UIKeyboardDidShowNotification + object:nil]; + + // For the sake of 4.X compatibility + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(inputKeyboardWillChangeFrame:) + name:@"UIKeyboardWillChangeFrameNotification" + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(inputKeyboardDidChangeFrame) + name:@"UIKeyboardDidChangeFrameNotification" + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(inputKeyboardWillHide:) + name:UIKeyboardWillHideNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(inputKeyboardDidHide) + name:UIKeyboardDidHideNotification + object:nil]; +} + +- (CGRect)keyboardFrameInView +{ + if (self.keyboardActiveView) + { + CGRect keyboardFrameInView = [self convertRect:self.keyboardActiveView.frame + fromView:self.keyboardActiveView.superview]; + return keyboardFrameInView; + } + else + { + CGRect keyboardFrameInView = CGRectMake(0.0f, + [[UIScreen mainScreen] bounds].size.height, + 0.0f, + 0.0f); + return keyboardFrameInView; + } +} + +- (void)removeKeyboardControl +{ + // Unregister for text input notifications + [[NSNotificationCenter defaultCenter] removeObserver:self + name:UITextFieldTextDidBeginEditingNotification + object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self + name:UITextViewTextDidBeginEditingNotification + object:nil]; + + // Unregister for keyboard notifications + [[NSNotificationCenter defaultCenter] removeObserver:self + name:UIKeyboardWillShowNotification + object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self + name:UIKeyboardDidShowNotification + object:nil]; + + // For the sake of 4.X compatibility + [[NSNotificationCenter defaultCenter] removeObserver:self + name:@"UIKeyboardWillChangeFrameNotification" + object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self + name:@"UIKeyboardDidChangeFrameNotification" + object:nil]; + + [[NSNotificationCenter defaultCenter] removeObserver:self + name:UIKeyboardWillHideNotification + object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self + name:UIKeyboardDidHideNotification + object:nil]; + + // Unregister any gesture recognizer + [self removeGestureRecognizer:self.keyboardPanRecognizer]; + + // Release a few properties + self.frameBasedKeyboardDidMoveBlock = nil; + self.keyboardActiveInput = nil; + self.keyboardActiveView = nil; + self.keyboardPanRecognizer = nil; +} + +- (void)hideKeyboard +{ + if (self.keyboardActiveView) + { + self.keyboardActiveView.hidden = YES; + self.keyboardActiveView.userInteractionEnabled = NO; + [self.keyboardActiveInput resignFirstResponder]; + } +} + +#pragma mark - Input Notifications + +- (void)responderDidBecomeActive:(NSNotification *)notification +{ + // Grab the active input, it will be used to find the keyboard view later on + self.keyboardActiveInput = notification.object; + if (!self.keyboardActiveInput.inputAccessoryView) + { + UITextField *textField = (UITextField *)self.keyboardActiveInput; + if ([textField respondsToSelector:@selector(setInputAccessoryView:)]) + { + UIView *nullView = [[UIView alloc] initWithFrame:CGRectZero]; + nullView.backgroundColor = [UIColor clearColor]; + textField.inputAccessoryView = nullView; + } + self.keyboardActiveInput = (UIResponder *)textField; + // Force the keyboard active view reset + [self inputKeyboardDidShow]; + } +} + +#pragma mark - Keyboard Notifications + +- (void)inputKeyboardWillShow:(NSNotification *)notification +{ + CGRect keyboardEndFrameWindow; + [[notification.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] getValue: &keyboardEndFrameWindow]; + + double keyboardTransitionDuration; + [[notification.userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&keyboardTransitionDuration]; + + UIViewAnimationCurve keyboardTransitionAnimationCurve; + [[notification.userInfo valueForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&keyboardTransitionAnimationCurve]; + + self.keyboardActiveView.hidden = NO; + self.keyboardOpened = YES; + + CGRect keyboardEndFrameView = [self convertRect:keyboardEndFrameWindow fromView:nil]; + + BOOL constraintBasedKeyboardDidMoveBlockCalled = self.constraintBasedKeyboardDidMoveBlock && !CGRectIsNull(keyboardEndFrameView); + if (constraintBasedKeyboardDidMoveBlockCalled) + self.constraintBasedKeyboardDidMoveBlock(keyboardEndFrameView, YES, NO); + + [UIView animateWithDuration:keyboardTransitionDuration + delay:0.0f + options:AnimationOptionsForCurve(keyboardTransitionAnimationCurve) | UIViewAnimationOptionBeginFromCurrentState + animations:^{ + if (constraintBasedKeyboardDidMoveBlockCalled) + [self layoutIfNeeded]; + if (self.frameBasedKeyboardDidMoveBlock && !CGRectIsNull(keyboardEndFrameView)) + self.frameBasedKeyboardDidMoveBlock(keyboardEndFrameView, YES, NO); + } + completion:^(__unused BOOL finished){ + if (self.panning && !self.keyboardPanRecognizer) + { + // Register for gesture recognizer calls + self.keyboardPanRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self + action:@selector(panGestureDidChange:)]; + [self.keyboardPanRecognizer setMinimumNumberOfTouches:1]; + [self.keyboardPanRecognizer setDelegate:self]; + [self.keyboardPanRecognizer setCancelsTouchesInView:NO]; + [self addGestureRecognizer:self.keyboardPanRecognizer]; + } + }]; +} + +- (void)inputKeyboardDidShow +{ + // Grab the keyboard view + self.keyboardActiveView = self.keyboardActiveInput.inputAccessoryView.superview; + self.keyboardActiveView.hidden = NO; + + // If the active keyboard view could not be found (UITextViews...), try again + if (!self.keyboardActiveView) + { + // Find the first responder on subviews and look re-assign first responder to it + self.keyboardActiveInput = [self recursiveFindFirstResponder:self]; + self.keyboardActiveView = self.keyboardActiveInput.inputAccessoryView.superview; + self.keyboardActiveView.hidden = NO; + } +} + +- (void)inputKeyboardWillChangeFrame:(NSNotification *)notification +{ + CGRect keyboardEndFrameWindow; + [[notification.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] getValue: &keyboardEndFrameWindow]; + + double keyboardTransitionDuration; + [[notification.userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&keyboardTransitionDuration]; + + UIViewAnimationCurve keyboardTransitionAnimationCurve; + [[notification.userInfo valueForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&keyboardTransitionAnimationCurve]; + + CGRect keyboardEndFrameView = [self convertRect:keyboardEndFrameWindow fromView:nil]; + + BOOL constraintBasedKeyboardDidMoveBlockCalled = self.constraintBasedKeyboardDidMoveBlock && !CGRectIsNull(keyboardEndFrameView); + if (constraintBasedKeyboardDidMoveBlockCalled) + self.constraintBasedKeyboardDidMoveBlock(keyboardEndFrameView, NO, NO); + + [UIView animateWithDuration:keyboardTransitionDuration + delay:0.0f + options:AnimationOptionsForCurve(keyboardTransitionAnimationCurve) | UIViewAnimationOptionBeginFromCurrentState + animations:^{ + if (constraintBasedKeyboardDidMoveBlockCalled) + [self layoutIfNeeded]; + + if (self.frameBasedKeyboardDidMoveBlock && !CGRectIsNull(keyboardEndFrameView)) + self.frameBasedKeyboardDidMoveBlock(keyboardEndFrameView, NO, NO); + } + completion:nil]; +} + +- (void)inputKeyboardDidChangeFrame +{ + // Nothing to see here +} + +- (void)inputKeyboardWillHide:(NSNotification *)notification +{ + CGRect keyboardEndFrameWindow; + [[notification.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] getValue: &keyboardEndFrameWindow]; + + double keyboardTransitionDuration; + [[notification.userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&keyboardTransitionDuration]; + + UIViewAnimationCurve keyboardTransitionAnimationCurve; + [[notification.userInfo valueForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&keyboardTransitionAnimationCurve]; + + CGRect keyboardEndFrameView = [self convertRect:keyboardEndFrameWindow fromView:nil]; + + BOOL constraintBasedKeyboardDidMoveBlockCalled = self.constraintBasedKeyboardDidMoveBlock && !CGRectIsNull(keyboardEndFrameView); + if (constraintBasedKeyboardDidMoveBlockCalled) + self.constraintBasedKeyboardDidMoveBlock(keyboardEndFrameView, NO, YES); + + [UIView animateWithDuration:keyboardTransitionDuration + delay:0.0f + options:AnimationOptionsForCurve(keyboardTransitionAnimationCurve) | UIViewAnimationOptionBeginFromCurrentState + animations:^{ + if (constraintBasedKeyboardDidMoveBlockCalled) + [self layoutIfNeeded]; + + if (self.frameBasedKeyboardDidMoveBlock && !CGRectIsNull(keyboardEndFrameView)) + self.frameBasedKeyboardDidMoveBlock(keyboardEndFrameView, NO, YES); + } + completion:^(__unused BOOL finished){ + // Remove gesture recognizer when keyboard is not showing + [self removeGestureRecognizer:self.keyboardPanRecognizer]; + self.keyboardPanRecognizer = nil; + }]; +} + +- (void)inputKeyboardDidHide +{ + self.keyboardActiveView.hidden = NO; + self.keyboardActiveView.userInteractionEnabled = YES; + self.keyboardActiveView = nil; + self.keyboardActiveInput = nil; + self.keyboardOpened = NO; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(__unused NSDictionary *)change + context:(__unused void *)context +{ + if([keyPath isEqualToString:@"frame"] && object == self.keyboardActiveView) + { + CGRect keyboardEndFrameWindow = [[object valueForKeyPath:keyPath] CGRectValue]; + CGRect keyboardEndFrameView = [self convertRect:keyboardEndFrameWindow fromView:self.keyboardActiveView.superview]; + + if (CGRectEqualToRect(keyboardEndFrameView, self.previousKeyboardRect)) return; + + if (!self.keyboardActiveView.hidden && !CGRectIsNull(keyboardEndFrameView)) + { + if (self.frameBasedKeyboardDidMoveBlock) + self.frameBasedKeyboardDidMoveBlock(keyboardEndFrameView, NO, NO); + if (self.constraintBasedKeyboardDidMoveBlock) + { + self.constraintBasedKeyboardDidMoveBlock(keyboardEndFrameView, NO, NO); + [self layoutIfNeeded]; + } + } + + self.previousKeyboardRect = keyboardEndFrameView; + } +} + +#pragma mark - Touches Management + +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer +{ + if (gestureRecognizer == self.keyboardPanRecognizer || otherGestureRecognizer == self.keyboardPanRecognizer) + { + return YES; + } + else + { + return NO; + } +} + +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch +{ + if (gestureRecognizer == self.keyboardPanRecognizer) + { + // Don't allow panning if inside the active input (unless SELF is a UITextView and the receiving view) + return (![touch.view isFirstResponder] || ([self isKindOfClass:[UITextView class]] && [self isEqual:touch.view])); + } + else + { + return YES; + } +} + +- (void)panGestureDidChange:(UIPanGestureRecognizer *)gesture +{ + if(!self.keyboardActiveView || !self.keyboardActiveInput || self.keyboardActiveView.hidden) + { + self.keyboardActiveInput = [self recursiveFindFirstResponder:self]; + self.keyboardActiveView = self.keyboardActiveInput.inputAccessoryView.superview; + self.keyboardActiveView.hidden = NO; + } + else + { + self.keyboardActiveView.hidden = NO; + } + + CGFloat keyboardViewHeight = self.keyboardActiveView.bounds.size.height; + CGFloat keyboardWindowHeight = self.keyboardActiveView.superview.bounds.size.height; + CGPoint touchLocationInKeyboardWindow = [gesture locationInView:self.keyboardActiveView.superview]; + + // If touch is inside trigger offset, then disable keyboard input + if (touchLocationInKeyboardWindow.y > keyboardWindowHeight - keyboardViewHeight - self.keyboardTriggerOffset) + { + self.keyboardActiveView.userInteractionEnabled = NO; + } + else + { + self.keyboardActiveView.userInteractionEnabled = YES; + } + + switch (gesture.state) + { + case UIGestureRecognizerStateBegan: + { + // For the duration of this gesture, do not recognize more touches than + // it started with + gesture.maximumNumberOfTouches = gesture.numberOfTouches; + } + break; + case UIGestureRecognizerStateChanged: + { + CGRect newKeyboardViewFrame = self.keyboardActiveView.frame; + newKeyboardViewFrame.origin.y = touchLocationInKeyboardWindow.y + self.keyboardTriggerOffset; + // Bound the keyboard to the bottom of the screen + newKeyboardViewFrame.origin.y = MIN(newKeyboardViewFrame.origin.y, keyboardWindowHeight); + newKeyboardViewFrame.origin.y = MAX(newKeyboardViewFrame.origin.y, keyboardWindowHeight - keyboardViewHeight); + + // Only update if the frame has actually changed + if (newKeyboardViewFrame.origin.y != self.keyboardActiveView.frame.origin.y) + { + [UIView animateWithDuration:0.0f + delay:0.0f + options:UIViewAnimationOptionTransitionNone | UIViewAnimationOptionBeginFromCurrentState + animations:^{ + [self.keyboardActiveView setFrame:newKeyboardViewFrame]; + /* Unnecessary now, due to KVO on self.keyboardActiveView + CGRect newKeyboardViewFrameInView = [self convertRect:newKeyboardViewFrame + fromView:self.keyboardActiveView.window]; + if (self.frameBasedKeyboardDidMoveBlock) + self.frameBasedKeyboardDidMoveBlock(newKeyboardViewFrameInView); + */ + } + completion:nil]; + } + } + break; + case UIGestureRecognizerStateEnded: + case UIGestureRecognizerStateCancelled: + { + CGFloat thresholdHeight = keyboardWindowHeight - keyboardViewHeight - self.keyboardTriggerOffset + 44.0f; + CGPoint velocity = [gesture velocityInView:self.keyboardActiveView]; + BOOL shouldRecede; + + if (touchLocationInKeyboardWindow.y < thresholdHeight || velocity.y < 0) + shouldRecede = NO; + else + shouldRecede = YES; + + // If the keyboard has only been pushed down 44 pixels or has been + // panned upwards let it pop back up; otherwise, let it drop down + CGRect newKeyboardViewFrame = self.keyboardActiveView.frame; + newKeyboardViewFrame.origin.y = (!shouldRecede ? keyboardWindowHeight - keyboardViewHeight : keyboardWindowHeight); + + [UIView animateWithDuration:0.25f + delay:0.0f + options:UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState + animations:^{ + [self.keyboardActiveView setFrame:newKeyboardViewFrame]; + /* Unnecessary now, due to KVO on self.keyboardActiveView + CGRect newKeyboardViewFrameInView = [self convertRect:newKeyboardViewFrame + fromView:self.keyboardActiveView.window]; + if (self.frameBasedKeyboardDidMoveBlock) + self.frameBasedKeyboardDidMoveBlock(newKeyboardViewFrameInView); + */ + } + completion:^(__unused BOOL finished){ + [[self keyboardActiveView] setUserInteractionEnabled:!shouldRecede]; + if (shouldRecede) + { + [self hideKeyboard]; + } + }]; + + // Set the max number of touches back to the default + gesture.maximumNumberOfTouches = NSUIntegerMax; + } + break; + default: + break; + } +} + +#pragma mark - Internal Methods + +- (UIView *)recursiveFindFirstResponder:(UIView *)view +{ + if ([view isFirstResponder]) + { + return view; + } + UIView *found = nil; + for (UIView *v in view.subviews) + { + found = [self recursiveFindFirstResponder:v]; + if (found) + { + break; + } + } + return found; +} + +- (void)swizzled_addSubview:(UIView *)subview +{ + if (!subview.inputAccessoryView) + { + if ([subview isKindOfClass:[UITextField class]]) + { + UITextField *textField = (UITextField *)subview; + if ([textField respondsToSelector:@selector(setInputAccessoryView:)]) + { + UIView *nullView = [[UIView alloc] initWithFrame:CGRectZero]; + nullView.backgroundColor = [UIColor clearColor]; + textField.inputAccessoryView = nullView; + } + } + else if ([subview isKindOfClass:[UITextView class]]) { + UITextView *textView = (UITextView *)subview; + if ([textView respondsToSelector:@selector(setInputAccessoryView:)] && [textView respondsToSelector:@selector(isEditable)] && textView.isEditable) + { + UIView *nullView = [[UIView alloc] initWithFrame:CGRectZero]; + nullView.backgroundColor = [UIColor clearColor]; + textView.inputAccessoryView = nullView; + } + } + } + [self swizzled_addSubview:subview]; +} + +#pragma mark - Property Methods + +-(CGRect)previousKeyboardRect { + id previousRectValue = objc_getAssociatedObject(self, &UIViewPreviousKeyboardRect); + if (previousRectValue) + return [previousRectValue CGRectValue]; + + return CGRectZero; +} + +-(void)setPreviousKeyboardRect:(CGRect)previousKeyboardRect { + [self willChangeValueForKey:@"previousKeyboardRect"]; + objc_setAssociatedObject(self, + &UIViewPreviousKeyboardRect, + [NSValue valueWithCGRect:previousKeyboardRect], + OBJC_ASSOCIATION_RETAIN_NONATOMIC); + [self didChangeValueForKey:@"previousKeyboardRect"]; +} + +- (DAKeyboardDidMoveBlock)frameBasedKeyboardDidMoveBlock +{ + return objc_getAssociatedObject(self, + &UIViewKeyboardDidMoveFrameBasedBlock); +} + +- (void)setFrameBasedKeyboardDidMoveBlock:(DAKeyboardDidMoveBlock)frameBasedKeyboardDidMoveBlock +{ + [self willChangeValueForKey:@"frameBasedKeyboardDidMoveBlock"]; + objc_setAssociatedObject(self, + &UIViewKeyboardDidMoveFrameBasedBlock, + frameBasedKeyboardDidMoveBlock, + OBJC_ASSOCIATION_COPY); + [self didChangeValueForKey:@"frameBasedKeyboardDidMoveBlock"]; +} + +- (DAKeyboardDidMoveBlock)constraintBasedKeyboardDidMoveBlock +{ + return objc_getAssociatedObject(self, + &UIViewKeyboardDidMoveConstraintBasedBlock); +} + +- (void)setConstraintBasedKeyboardDidMoveBlock:(DAKeyboardDidMoveBlock)constraintBasedKeyboardDidMoveBlock +{ + [self willChangeValueForKey:@"constraintBasedKeyboardDidMoveBlock"]; + objc_setAssociatedObject(self, + &UIViewKeyboardDidMoveConstraintBasedBlock, + constraintBasedKeyboardDidMoveBlock, + OBJC_ASSOCIATION_COPY); + [self didChangeValueForKey:@"constraintBasedKeyboardDidMoveBlock"]; +} + +- (CGFloat)keyboardTriggerOffset +{ + NSNumber *keyboardTriggerOffsetNumber = objc_getAssociatedObject(self, + &UIViewKeyboardTriggerOffset); + return [keyboardTriggerOffsetNumber floatValue]; +} + +- (void)setKeyboardTriggerOffset:(CGFloat)keyboardTriggerOffset +{ + [self willChangeValueForKey:@"keyboardTriggerOffset"]; + objc_setAssociatedObject(self, + &UIViewKeyboardTriggerOffset, + [NSNumber numberWithFloat:keyboardTriggerOffset], + OBJC_ASSOCIATION_RETAIN_NONATOMIC); + [self didChangeValueForKey:@"keyboardTriggerOffset"]; +} + +- (BOOL)isPanning +{ + NSNumber *isPanningNumber = objc_getAssociatedObject(self, + &UIViewIsPanning); + return [isPanningNumber boolValue]; +} + +- (void)setPanning:(BOOL)panning +{ + [self willChangeValueForKey:@"panning"]; + objc_setAssociatedObject(self, + &UIViewIsPanning, + @(panning), + OBJC_ASSOCIATION_RETAIN_NONATOMIC); + [self didChangeValueForKey:@"panning"]; +} + +- (UIResponder *)keyboardActiveInput +{ + return objc_getAssociatedObject(self, + &UIViewKeyboardActiveInput); +} + +- (void)setKeyboardActiveInput:(UIResponder *)keyboardActiveInput +{ + [self willChangeValueForKey:@"keyboardActiveInput"]; + objc_setAssociatedObject(self, + &UIViewKeyboardActiveInput, + keyboardActiveInput, + OBJC_ASSOCIATION_RETAIN); + [self didChangeValueForKey:@"keyboardActiveInput"]; +} + +- (UIView *)keyboardActiveView +{ + return objc_getAssociatedObject(self, + &UIViewKeyboardActiveView); +} + +- (void)setKeyboardActiveView:(UIView *)keyboardActiveView +{ + [self willChangeValueForKey:@"keyboardActiveView"]; + [self.keyboardActiveView removeObserver:self + forKeyPath:@"frame"]; + if (keyboardActiveView) + { + [keyboardActiveView addObserver:self + forKeyPath:@"frame" + options:0 + context:NULL]; + } + objc_setAssociatedObject(self, + &UIViewKeyboardActiveView, + keyboardActiveView, + OBJC_ASSOCIATION_RETAIN); + [self didChangeValueForKey:@"keyboardActiveView"]; +} + +- (UIPanGestureRecognizer *)keyboardPanRecognizer +{ + return objc_getAssociatedObject(self, + &UIViewKeyboardPanRecognizer); +} + +- (void)setKeyboardPanRecognizer:(UIPanGestureRecognizer *)keyboardPanRecognizer +{ + [self willChangeValueForKey:@"keyboardPanRecognizer"]; + objc_setAssociatedObject(self, + &UIViewKeyboardPanRecognizer, + keyboardPanRecognizer, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); + [self didChangeValueForKey:@"keyboardPanRecognizer"]; +} + +- (BOOL)isKeyboardOpened +{ + return [objc_getAssociatedObject(self, + &UIViewKeyboardOpened) boolValue]; +} + +- (void)setKeyboardOpened:(BOOL)keyboardOpened +{ + [self willChangeValueForKey:@"keyboardOpened"]; + objc_setAssociatedObject(self, + &UIViewKeyboardOpened, + @(keyboardOpened), + OBJC_ASSOCIATION_RETAIN_NONATOMIC); + [self didChangeValueForKey:@"keyboardOpened"]; +} + +- (BOOL)keyboardWillRecede +{ + CGFloat keyboardViewHeight = self.keyboardActiveView.bounds.size.height; + CGFloat keyboardWindowHeight = self.keyboardActiveView.superview.bounds.size.height; + CGPoint touchLocationInKeyboardWindow = [self.keyboardPanRecognizer locationInView:self.keyboardActiveView.superview]; + + CGFloat thresholdHeight = keyboardWindowHeight - keyboardViewHeight - self.keyboardTriggerOffset + 44.0f; + CGPoint velocity = [self.keyboardPanRecognizer velocityInView:self.keyboardActiveView]; + + return touchLocationInKeyboardWindow.y >= thresholdHeight && velocity.y >= 0; +} + +@end \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/HPGrowingTextView.h b/Whatsapp-Swift/Whatsapp-Swift/HPGrowingTextView.h new file mode 100755 index 0000000..8b343c5 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/HPGrowingTextView.h @@ -0,0 +1,126 @@ +// +// HPTextView.h +// +// Created by Hans Pinckaers on 29-06-10. +// +// MIT License +// +// Copyright (c) 2011 Hans Pinckaers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +#if __IPHONE_OS_VERSION_MAX_ALLOWED < 60000 + // UITextAlignment is deprecated in iOS 6.0+, use NSTextAlignment instead. + // Reference: https://developer.apple.com/library/ios/documentation/uikit/reference/NSString_UIKit_Additions/Reference/Reference.html + #define NSTextAlignment UITextAlignment +#endif + +@class HPGrowingTextView; +@class HPTextViewInternal; + +@protocol HPGrowingTextViewDelegate + +@optional +- (BOOL)growingTextViewShouldBeginEditing:(HPGrowingTextView *)growingTextView; +- (BOOL)growingTextViewShouldEndEditing:(HPGrowingTextView *)growingTextView; + +- (void)growingTextViewDidBeginEditing:(HPGrowingTextView *)growingTextView; +- (void)growingTextViewDidEndEditing:(HPGrowingTextView *)growingTextView; + +- (BOOL)growingTextView:(HPGrowingTextView *)growingTextView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text; +- (void)growingTextViewDidChange:(HPGrowingTextView *)growingTextView; + +- (void)growingTextView:(HPGrowingTextView *)growingTextView willChangeHeight:(float)height; +- (void)growingTextView:(HPGrowingTextView *)growingTextView didChangeHeight:(float)height; + +- (void)growingTextViewDidChangeSelection:(HPGrowingTextView *)growingTextView; +- (BOOL)growingTextViewShouldReturn:(HPGrowingTextView *)growingTextView; +@end + +@interface HPGrowingTextView : UIView { + HPTextViewInternal *internalTextView; + + int minHeight; + int maxHeight; + + //class properties + int maxNumberOfLines; + int minNumberOfLines; + + BOOL animateHeightChange; + NSTimeInterval animationDuration; + + //uitextview properties + NSObject *__unsafe_unretained delegate; + NSTextAlignment textAlignment; + NSRange selectedRange; + BOOL editable; + UIDataDetectorTypes dataDetectorTypes; + UIReturnKeyType returnKeyType; + UIKeyboardType keyboardType; + + UIEdgeInsets contentInset; +} + +//real class properties +@property int maxNumberOfLines; +@property int minNumberOfLines; +@property (nonatomic) int maxHeight; +@property (nonatomic) int minHeight; +@property BOOL animateHeightChange; +@property NSTimeInterval animationDuration; +@property (nonatomic, strong) NSString *placeholder; +@property (nonatomic, strong) UIColor *placeholderColor; +@property (nonatomic, strong) UITextView *internalTextView; + + +//uitextview properties +@property(unsafe_unretained) NSObject *delegate; +@property(nonatomic,strong) NSString *text; +@property(nonatomic,strong) UIFont *font; +@property(nonatomic,strong) UIColor *textColor; +@property(nonatomic) NSTextAlignment textAlignment; // default is NSTextAlignmentLeft +@property(nonatomic) NSRange selectedRange; // only ranges of length 0 are supported +@property(nonatomic,getter=isEditable) BOOL editable; +@property(nonatomic) UIDataDetectorTypes dataDetectorTypes __OSX_AVAILABLE_STARTING(__MAC_NA, __IPHONE_3_0); +@property (nonatomic) UIReturnKeyType returnKeyType; +@property (nonatomic) UIKeyboardType keyboardType; +@property (assign) UIEdgeInsets contentInset; +@property (nonatomic) BOOL isScrollable; +@property(nonatomic) BOOL enablesReturnKeyAutomatically; + +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 +- (id)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer; +#endif + +//uitextview methods +//need others? use .internalTextView +- (BOOL)becomeFirstResponder; +- (BOOL)resignFirstResponder; +- (BOOL)isFirstResponder; + +- (BOOL)hasText; +- (void)scrollRangeToVisible:(NSRange)range; + +// call to force a height change (e.g. after you change max/min lines) +- (void)refreshHeight; + +@end diff --git a/Whatsapp-Swift/Whatsapp-Swift/HPGrowingTextView.m b/Whatsapp-Swift/Whatsapp-Swift/HPGrowingTextView.m new file mode 100755 index 0000000..9871c6f --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/HPGrowingTextView.m @@ -0,0 +1,666 @@ +// +// HPTextView.m +// +// Created by Hans Pinckaers on 29-06-10. +// +// MIT License +// +// Copyright (c) 2011 Hans Pinckaers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "HPGrowingTextView.h" +#import "HPTextViewInternal.h" + +@interface HPGrowingTextView(private) +-(void)commonInitialiser; +-(void)resizeTextView:(NSInteger)newSizeH; +-(void)growDidStop; +@end + +@implementation HPGrowingTextView +@synthesize internalTextView; +@synthesize delegate; +@synthesize maxHeight; +@synthesize minHeight; +@synthesize font; +@synthesize textColor; +@synthesize textAlignment; +@synthesize selectedRange; +@synthesize editable; +@synthesize dataDetectorTypes; +@synthesize animateHeightChange; +@synthesize animationDuration; +@synthesize returnKeyType; +@dynamic placeholder; +@dynamic placeholderColor; + +// having initwithcoder allows us to use HPGrowingTextView in a Nib. -- aob, 9/2011 +- (id)initWithCoder:(NSCoder *)aDecoder +{ + if ((self = [super initWithCoder:aDecoder])) { + [self commonInitialiser]; + } + return self; +} + +- (id)initWithFrame:(CGRect)frame { + if ((self = [super initWithFrame:frame])) { + [self commonInitialiser]; + } + return self; +} + +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 +- (id)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer { + if ((self = [super initWithFrame:frame])) { + [self commonInitialiser:textContainer]; + } + return self; +} + +-(void)commonInitialiser { + [self commonInitialiser:nil]; +} + +-(void)commonInitialiser:(NSTextContainer *)textContainer +#else +-(void)commonInitialiser +#endif +{ + // Initialization code + CGRect r = self.frame; + r.origin.y = 0; + r.origin.x = 0; +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 + internalTextView = [[HPTextViewInternal alloc] initWithFrame:r textContainer:textContainer]; +#else + internalTextView = [[HPTextViewInternal alloc] initWithFrame:r]; +#endif + internalTextView.delegate = self; + internalTextView.scrollEnabled = NO; + internalTextView.font = [UIFont fontWithName:@"Helvetica" size:13]; + internalTextView.contentInset = UIEdgeInsetsZero; + internalTextView.showsHorizontalScrollIndicator = NO; + internalTextView.text = @"-"; + internalTextView.contentMode = UIViewContentModeRedraw; + [self addSubview:internalTextView]; + + minHeight = internalTextView.frame.size.height; + minNumberOfLines = 1; + + animateHeightChange = YES; + animationDuration = 0.1f; + + internalTextView.text = @""; + + [self setMaxNumberOfLines:3]; + + [self setPlaceholderColor:[UIColor lightGrayColor]]; + internalTextView.displayPlaceHolder = YES; +} + +-(CGSize)sizeThatFits:(CGSize)size +{ + if (self.text.length == 0) { + size.height = minHeight; + } + return size; +} + +-(void)layoutSubviews +{ + [super layoutSubviews]; + + CGRect r = self.bounds; + r.origin.y = 0; + r.origin.x = contentInset.left; + r.size.width -= contentInset.left + contentInset.right; + + internalTextView.frame = r; +} + +-(void)setContentInset:(UIEdgeInsets)inset +{ + contentInset = inset; + + CGRect r = self.frame; + r.origin.y = inset.top - inset.bottom; + r.origin.x = inset.left; + r.size.width -= inset.left + inset.right; + + internalTextView.frame = r; + + [self setMaxNumberOfLines:maxNumberOfLines]; + [self setMinNumberOfLines:minNumberOfLines]; +} + +-(UIEdgeInsets)contentInset +{ + return contentInset; +} + +-(void)setMaxNumberOfLines:(int)n +{ + if(n == 0 && maxHeight > 0) return; // the user specified a maxHeight themselves. + + // Use internalTextView for height calculations, thanks to Gwynne + NSString *saveText = internalTextView.text, *newText = @"-"; + + internalTextView.delegate = nil; + internalTextView.hidden = YES; + + for (int i = 1; i < n; ++i) + newText = [newText stringByAppendingString:@"\n|W|"]; + + internalTextView.text = newText; + + maxHeight = [self measureHeight]; + + internalTextView.text = saveText; + internalTextView.hidden = NO; + internalTextView.delegate = self; + + [self sizeToFit]; + + maxNumberOfLines = n; +} + +-(int)maxNumberOfLines +{ + return maxNumberOfLines; +} + +- (void)setMaxHeight:(int)height +{ + maxHeight = height; + maxNumberOfLines = 0; +} + +-(void)setMinNumberOfLines:(int)m +{ + if(m == 0 && minHeight > 0) return; // the user specified a minHeight themselves. + + // Use internalTextView for height calculations, thanks to Gwynne + NSString *saveText = internalTextView.text, *newText = @"-"; + + internalTextView.delegate = nil; + internalTextView.hidden = YES; + + for (int i = 1; i < m; ++i) + newText = [newText stringByAppendingString:@"\n|W|"]; + + internalTextView.text = newText; + + minHeight = [self measureHeight]; + + internalTextView.text = saveText; + internalTextView.hidden = NO; + internalTextView.delegate = self; + + [self sizeToFit]; + + minNumberOfLines = m; +} + +-(int)minNumberOfLines +{ + return minNumberOfLines; +} + +- (void)setMinHeight:(int)height +{ + minHeight = height; + minNumberOfLines = 0; +} + +- (NSString *)placeholder +{ + return internalTextView.placeholder; +} + +- (void)setPlaceholder:(NSString *)placeholder +{ + [internalTextView setPlaceholder:placeholder]; + [internalTextView setNeedsDisplay]; +} + +- (UIColor *)placeholderColor +{ + return internalTextView.placeholderColor; +} + +- (void)setPlaceholderColor:(UIColor *)placeholderColor +{ + [internalTextView setPlaceholderColor:placeholderColor]; +} + +- (void)textViewDidChange:(UITextView *)textView +{ + [self refreshHeight]; +} + +- (void)refreshHeight +{ + //size of content, so we can set the frame of self + NSInteger newSizeH = [self measureHeight]; + if (newSizeH < minHeight || !internalTextView.hasText) { + newSizeH = minHeight; //not smalles than minHeight + } + else if (maxHeight && newSizeH > maxHeight) { + newSizeH = maxHeight; // not taller than maxHeight + } + + if (internalTextView.frame.size.height != newSizeH) + { + // if our new height is greater than the maxHeight + // sets not set the height or move things + // around and enable scrolling + if (newSizeH >= maxHeight) + { + if(!internalTextView.scrollEnabled){ + internalTextView.scrollEnabled = YES; + [internalTextView flashScrollIndicators]; + } + + } else { + internalTextView.scrollEnabled = NO; + } + + // [fixed] Pasting too much text into the view failed to fire the height change, + // thanks to Gwynne + if (newSizeH <= maxHeight) + { + if(animateHeightChange) { + + if ([UIView resolveClassMethod:@selector(animateWithDuration:animations:)]) { +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000 + [UIView animateWithDuration:animationDuration + delay:0 + options:(UIViewAnimationOptionAllowUserInteraction| + UIViewAnimationOptionBeginFromCurrentState) + animations:^(void) { + [self resizeTextView:newSizeH]; + } + completion:^(BOOL finished) { + if ([delegate respondsToSelector:@selector(growingTextView:didChangeHeight:)]) { + [delegate growingTextView:self didChangeHeight:newSizeH]; + } + }]; +#endif + } else { + [UIView beginAnimations:@"" context:nil]; + [UIView setAnimationDuration:animationDuration]; + [UIView setAnimationDelegate:self]; + [UIView setAnimationDidStopSelector:@selector(growDidStop)]; + [UIView setAnimationBeginsFromCurrentState:YES]; + [self resizeTextView:newSizeH]; + [UIView commitAnimations]; + } + } else { + [self resizeTextView:newSizeH]; + // [fixed] The growingTextView:didChangeHeight: delegate method was not called at all when not animating height changes. + // thanks to Gwynne + + if ([delegate respondsToSelector:@selector(growingTextView:didChangeHeight:)]) { + [delegate growingTextView:self didChangeHeight:newSizeH]; + } + } + } + } + // Display (or not) the placeholder string + + BOOL wasDisplayingPlaceholder = internalTextView.displayPlaceHolder; + internalTextView.displayPlaceHolder = self.internalTextView.text.length == 0; + + if (wasDisplayingPlaceholder != internalTextView.displayPlaceHolder) { + [internalTextView setNeedsDisplay]; + } + + + // scroll to caret (needed on iOS7) + if ([self respondsToSelector:@selector(snapshotViewAfterScreenUpdates:)]) + { + [self performSelector:@selector(resetScrollPositionForIOS7) withObject:nil afterDelay:0.1f]; + } + + // Tell the delegate that the text view changed + if ([delegate respondsToSelector:@selector(growingTextViewDidChange:)]) { + [delegate growingTextViewDidChange:self]; + } +} + +// Code from apple developer forum - @Steve Krulewitz, @Mark Marszal, @Eric Silverberg +- (CGFloat)measureHeight +{ + if ([self respondsToSelector:@selector(snapshotViewAfterScreenUpdates:)]) + { + return ceilf([self.internalTextView sizeThatFits:self.internalTextView.frame.size].height); + } + else { + return self.internalTextView.contentSize.height; + } +} + +- (void)resetScrollPositionForIOS7 +{ + CGRect r = [internalTextView caretRectForPosition:internalTextView.selectedTextRange.end]; + CGFloat caretY = MAX(r.origin.y - internalTextView.frame.size.height + r.size.height + 8, 0); + if (internalTextView.contentOffset.y < caretY && r.origin.y != INFINITY) + internalTextView.contentOffset = CGPointMake(0, caretY); +} + +-(void)resizeTextView:(NSInteger)newSizeH +{ + if ([delegate respondsToSelector:@selector(growingTextView:willChangeHeight:)]) { + [delegate growingTextView:self willChangeHeight:newSizeH]; + } + + CGRect internalTextViewFrame = self.frame; + internalTextViewFrame.size.height = newSizeH; // + padding + self.frame = internalTextViewFrame; + + internalTextViewFrame.origin.y = contentInset.top - contentInset.bottom; + internalTextViewFrame.origin.x = contentInset.left; + + if(!CGRectEqualToRect(internalTextView.frame, internalTextViewFrame)) internalTextView.frame = internalTextViewFrame; +} + +- (void)growDidStop +{ + // scroll to caret (needed on iOS7) + if ([self respondsToSelector:@selector(snapshotViewAfterScreenUpdates:)]) + { + [self resetScrollPositionForIOS7]; + } + + if ([delegate respondsToSelector:@selector(growingTextView:didChangeHeight:)]) { + [delegate growingTextView:self didChangeHeight:self.frame.size.height]; + } +} + +-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event +{ + [internalTextView becomeFirstResponder]; +} + +- (BOOL)becomeFirstResponder +{ + [super becomeFirstResponder]; + return [self.internalTextView becomeFirstResponder]; +} + +-(BOOL)resignFirstResponder +{ + [super resignFirstResponder]; + return [internalTextView resignFirstResponder]; +} + +-(BOOL)isFirstResponder +{ + return [self.internalTextView isFirstResponder]; +} + + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark UITextView properties +/////////////////////////////////////////////////////////////////////////////////////////////////// + +-(void)setText:(NSString *)newText +{ + internalTextView.text = newText; + + // include this line to analyze the height of the textview. + // fix from Ankit Thakur + [self performSelector:@selector(textViewDidChange:) withObject:internalTextView]; +} + +-(NSString*) text +{ + return internalTextView.text; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +-(void)setFont:(UIFont *)afont +{ + internalTextView.font= afont; + + [self setMaxNumberOfLines:maxNumberOfLines]; + [self setMinNumberOfLines:minNumberOfLines]; +} + +-(UIFont *)font +{ + return internalTextView.font; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +-(void)setTextColor:(UIColor *)color +{ + internalTextView.textColor = color; +} + +-(UIColor*)textColor{ + return internalTextView.textColor; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +-(void)setBackgroundColor:(UIColor *)backgroundColor +{ + [super setBackgroundColor:backgroundColor]; + internalTextView.backgroundColor = backgroundColor; +} + +-(UIColor*)backgroundColor +{ + return internalTextView.backgroundColor; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +-(void)setTextAlignment:(NSTextAlignment)aligment +{ + internalTextView.textAlignment = aligment; +} + +-(NSTextAlignment)textAlignment +{ + return internalTextView.textAlignment; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +-(void)setSelectedRange:(NSRange)range +{ + internalTextView.selectedRange = range; +} + +-(NSRange)selectedRange +{ + return internalTextView.selectedRange; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)setIsScrollable:(BOOL)isScrollable +{ + internalTextView.scrollEnabled = isScrollable; +} + +- (BOOL)isScrollable +{ + return internalTextView.scrollEnabled; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +-(void)setEditable:(BOOL)beditable +{ + internalTextView.editable = beditable; +} + +-(BOOL)isEditable +{ + return internalTextView.editable; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +-(void)setReturnKeyType:(UIReturnKeyType)keyType +{ + internalTextView.returnKeyType = keyType; +} + +-(UIReturnKeyType)returnKeyType +{ + return internalTextView.returnKeyType; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)setKeyboardType:(UIKeyboardType)keyType +{ + internalTextView.keyboardType = keyType; +} + +- (UIKeyboardType)keyboardType +{ + return internalTextView.keyboardType; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)setEnablesReturnKeyAutomatically:(BOOL)enablesReturnKeyAutomatically +{ + internalTextView.enablesReturnKeyAutomatically = enablesReturnKeyAutomatically; +} + +- (BOOL)enablesReturnKeyAutomatically +{ + return internalTextView.enablesReturnKeyAutomatically; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +-(void)setDataDetectorTypes:(UIDataDetectorTypes)datadetector +{ + internalTextView.dataDetectorTypes = datadetector; +} + +-(UIDataDetectorTypes)dataDetectorTypes +{ + return internalTextView.dataDetectorTypes; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)hasText{ + return [internalTextView hasText]; +} + +- (void)scrollRangeToVisible:(NSRange)range +{ + [internalTextView scrollRangeToVisible:range]; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark UITextViewDelegate + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +- (BOOL)textViewShouldBeginEditing:(UITextView *)textView { + if ([delegate respondsToSelector:@selector(growingTextViewShouldBeginEditing:)]) { + return [delegate growingTextViewShouldBeginEditing:self]; + + } else { + return YES; + } +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +- (BOOL)textViewShouldEndEditing:(UITextView *)textView { + if ([delegate respondsToSelector:@selector(growingTextViewShouldEndEditing:)]) { + return [delegate growingTextViewShouldEndEditing:self]; + + } else { + return YES; + } +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +- (void)textViewDidBeginEditing:(UITextView *)textView { + if ([delegate respondsToSelector:@selector(growingTextViewDidBeginEditing:)]) { + [delegate growingTextViewDidBeginEditing:self]; + } +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +- (void)textViewDidEndEditing:(UITextView *)textView { + if ([delegate respondsToSelector:@selector(growingTextViewDidEndEditing:)]) { + [delegate growingTextViewDidEndEditing:self]; + } +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range + replacementText:(NSString *)atext { + + //weird 1 pixel bug when clicking backspace when textView is empty + if(![textView hasText] && [atext isEqualToString:@""]) return NO; + + //Added by bretdabaker: sometimes we want to handle this ourselves + if ([delegate respondsToSelector:@selector(growingTextView:shouldChangeTextInRange:replacementText:)]) + return [delegate growingTextView:self shouldChangeTextInRange:range replacementText:atext]; + + if ([atext isEqualToString:@"\n"]) { + if ([delegate respondsToSelector:@selector(growingTextViewShouldReturn:)]) { + if (![delegate performSelector:@selector(growingTextViewShouldReturn:) withObject:self]) { + return YES; + } else { + [textView resignFirstResponder]; + return NO; + } + } + } + + return YES; + + +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +- (void)textViewDidChangeSelection:(UITextView *)textView { + if ([delegate respondsToSelector:@selector(growingTextViewDidChangeSelection:)]) { + [delegate growingTextViewDidChangeSelection:self]; + } +} + + + +@end diff --git a/Whatsapp-Swift/Whatsapp-Swift/HPTextViewInternal.h b/Whatsapp-Swift/Whatsapp-Swift/HPTextViewInternal.h new file mode 100755 index 0000000..175f4d4 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/HPTextViewInternal.h @@ -0,0 +1,37 @@ +// +// HPTextViewInternal.h +// +// Created by Hans Pinckaers on 29-06-10. +// +// MIT License +// +// Copyright (c) 2011 Hans Pinckaers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + + +@interface HPTextViewInternal : UITextView + +@property (nonatomic, strong) NSString *placeholder; +@property (nonatomic, strong) UIColor *placeholderColor; +@property (nonatomic) BOOL displayPlaceHolder; + +@end diff --git a/Whatsapp-Swift/Whatsapp-Swift/HPTextViewInternal.m b/Whatsapp-Swift/Whatsapp-Swift/HPTextViewInternal.m new file mode 100755 index 0000000..e007bae --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/HPTextViewInternal.m @@ -0,0 +1,127 @@ +// +// HPTextViewInternal.m +// +// Created by Hans Pinckaers on 29-06-10. +// +// MIT License +// +// Copyright (c) 2011 Hans Pinckaers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "HPTextViewInternal.h" + + +@implementation HPTextViewInternal + +-(void)setText:(NSString *)text +{ + BOOL originalValue = self.scrollEnabled; + //If one of GrowingTextView's superviews is a scrollView, and self.scrollEnabled == NO, + //setting the text programatically will cause UIKit to search upwards until it finds a scrollView with scrollEnabled==yes + //then scroll it erratically. Setting scrollEnabled temporarily to YES prevents this. + [self setScrollEnabled:YES]; + [super setText:text]; + [self setScrollEnabled:originalValue]; +} + +- (void)setScrollable:(BOOL)isScrollable +{ + [super setScrollEnabled:isScrollable]; +} + +-(void)setContentOffset:(CGPoint)s +{ + if(self.tracking || self.decelerating){ + //initiated by user... + + UIEdgeInsets insets = self.contentInset; + insets.bottom = 0; + insets.top = 0; + self.contentInset = insets; + + } else { + + float bottomOffset = (self.contentSize.height - self.frame.size.height + self.contentInset.bottom); + if(s.y < bottomOffset && self.scrollEnabled){ + UIEdgeInsets insets = self.contentInset; + insets.bottom = 8; + insets.top = 0; + self.contentInset = insets; + } + } + + // Fix "overscrolling" bug + if (s.y > self.contentSize.height - self.frame.size.height && !self.decelerating && !self.tracking && !self.dragging) + s = CGPointMake(s.x, self.contentSize.height - self.frame.size.height); + + [super setContentOffset:s]; +} + +-(void)setContentInset:(UIEdgeInsets)s +{ + UIEdgeInsets insets = s; + + if(s.bottom>8) insets.bottom = 0; + insets.top = 0; + + [super setContentInset:insets]; +} + +-(void)setContentSize:(CGSize)contentSize +{ + // is this an iOS5 bug? Need testing! + if(self.contentSize.height > contentSize.height) + { + UIEdgeInsets insets = self.contentInset; + insets.bottom = 0; + insets.top = 0; + self.contentInset = insets; + } + + [super setContentSize:contentSize]; +} + +- (void)drawRect:(CGRect)rect +{ + [super drawRect:rect]; + if (self.displayPlaceHolder && self.placeholder && self.placeholderColor) + { + if ([self respondsToSelector:@selector(snapshotViewAfterScreenUpdates:)]) + { + NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init]; + paragraphStyle.alignment = self.textAlignment; + [self.placeholder drawInRect:CGRectMake(5, 8 + self.contentInset.top, self.frame.size.width-self.contentInset.left, self.frame.size.height- self.contentInset.top) withAttributes:@{NSFontAttributeName:self.font, NSForegroundColorAttributeName:self.placeholderColor, NSParagraphStyleAttributeName:paragraphStyle}]; + } + else { + [self.placeholderColor set]; + + NSDictionary *dictionary = [[NSDictionary alloc] initWithObjectsAndKeys: self.font, NSFontAttributeName, nil]; + [self.placeholder drawInRect:CGRectMake(8.0f, 8.0f, self.frame.size.width - 16.0f, self.frame.size.height - 16.0f) withAttributes:dictionary]; + } + } +} + +-(void)setPlaceholder:(NSString *)placeholder +{ + _placeholder = placeholder; + [self setNeedsDisplay]; +} + +@end diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/Contents.json b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/app default/AppIcon.appiconset/Contents.json b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/app default/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..118c98f --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/app default/AppIcon.appiconset/Contents.json @@ -0,0 +1,38 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/app default/Brand Assets.launchimage/Contents.json b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/app default/Brand Assets.launchimage/Contents.json new file mode 100644 index 0000000..a0ad363 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/app default/Brand Assets.launchimage/Contents.json @@ -0,0 +1,36 @@ +{ + "images" : [ + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/app default/Contents.json b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/app default/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/app default/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/app default/LaunchImage.launchimage/Contents.json b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/app default/LaunchImage.launchimage/Contents.json new file mode 100644 index 0000000..fadb823 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/app default/LaunchImage.launchimage/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "subtype" : "retina4", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/background/chat background.imageset/Contents.json b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/background/chat background.imageset/Contents.json new file mode 100644 index 0000000..88bb437 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/background/chat background.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "Layer 1.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "Layer 1@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/background/chat background.imageset/Layer 1.png b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/background/chat background.imageset/Layer 1.png new file mode 100644 index 0000000..19e198a Binary files /dev/null and b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/background/chat background.imageset/Layer 1.png differ diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/background/chat background.imageset/Layer 1@2x.png b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/background/chat background.imageset/Layer 1@2x.png new file mode 100644 index 0000000..69b472c Binary files /dev/null and b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/background/chat background.imageset/Layer 1@2x.png differ diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/bubbles/bubbleMine.imageset/BubbleOutgoing.png b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/bubbles/bubbleMine.imageset/BubbleOutgoing.png new file mode 100644 index 0000000..f514df8 Binary files /dev/null and b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/bubbles/bubbleMine.imageset/BubbleOutgoing.png differ diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/bubbles/bubbleMine.imageset/BubbleOutgoing@2x.png b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/bubbles/bubbleMine.imageset/BubbleOutgoing@2x.png new file mode 100644 index 0000000..912a8d3 Binary files /dev/null and b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/bubbles/bubbleMine.imageset/BubbleOutgoing@2x.png differ diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/bubbles/bubbleMine.imageset/BubbleOutgoing@3x.png b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/bubbles/bubbleMine.imageset/BubbleOutgoing@3x.png new file mode 100644 index 0000000..7b47a37 Binary files /dev/null and b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/bubbles/bubbleMine.imageset/BubbleOutgoing@3x.png differ diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/bubbles/bubbleMine.imageset/Contents.json b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/bubbles/bubbleMine.imageset/Contents.json new file mode 100644 index 0000000..569f945 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/bubbles/bubbleMine.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "BubbleOutgoing.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "BubbleOutgoing@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "BubbleOutgoing@3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/bubbles/bubbleSomeone.imageset/BubbleIncoming.png b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/bubbles/bubbleSomeone.imageset/BubbleIncoming.png new file mode 100644 index 0000000..7559e9b Binary files /dev/null and b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/bubbles/bubbleSomeone.imageset/BubbleIncoming.png differ diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/bubbles/bubbleSomeone.imageset/BubbleIncoming@2x.png b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/bubbles/bubbleSomeone.imageset/BubbleIncoming@2x.png new file mode 100644 index 0000000..c9991e7 Binary files /dev/null and b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/bubbles/bubbleSomeone.imageset/BubbleIncoming@2x.png differ diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/bubbles/bubbleSomeone.imageset/BubbleIncoming@3x.png b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/bubbles/bubbleSomeone.imageset/BubbleIncoming@3x.png new file mode 100644 index 0000000..6213164 Binary files /dev/null and b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/bubbles/bubbleSomeone.imageset/BubbleIncoming@3x.png differ diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/bubbles/bubbleSomeone.imageset/Contents.json b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/bubbles/bubbleSomeone.imageset/Contents.json new file mode 100644 index 0000000..125fa79 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/bubbles/bubbleSomeone.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "BubbleIncoming.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "BubbleIncoming@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "BubbleIncoming@3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/inputbar/share.imageset/Contents.json b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/inputbar/share.imageset/Contents.json new file mode 100644 index 0000000..0e69c28 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/inputbar/share.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "share.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "share2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/inputbar/share.imageset/share.png b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/inputbar/share.imageset/share.png new file mode 100644 index 0000000..ca56ff7 Binary files /dev/null and b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/inputbar/share.imageset/share.png differ diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/inputbar/share.imageset/share2x.png b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/inputbar/share.imageset/share2x.png new file mode 100644 index 0000000..aaea558 Binary files /dev/null and b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/inputbar/share.imageset/share2x.png differ diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_failed.imageset/Alert7.png b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_failed.imageset/Alert7.png new file mode 100644 index 0000000..755cbde Binary files /dev/null and b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_failed.imageset/Alert7.png differ diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_failed.imageset/Alert7@2x.png b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_failed.imageset/Alert7@2x.png new file mode 100644 index 0000000..4b09145 Binary files /dev/null and b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_failed.imageset/Alert7@2x.png differ diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_failed.imageset/Contents.json b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_failed.imageset/Contents.json new file mode 100644 index 0000000..4e449f5 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_failed.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "Alert7.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "Alert7@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_notified.imageset/Contents.json b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_notified.imageset/Contents.json new file mode 100644 index 0000000..e71a2e5 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_notified.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "notified.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "notified@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_notified.imageset/notified.png b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_notified.imageset/notified.png new file mode 100644 index 0000000..eb0d7b6 Binary files /dev/null and b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_notified.imageset/notified.png differ diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_notified.imageset/notified@2x.png b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_notified.imageset/notified@2x.png new file mode 100644 index 0000000..88fafe7 Binary files /dev/null and b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_notified.imageset/notified@2x.png differ diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_read.imageset/Contents.json b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_read.imageset/Contents.json new file mode 100644 index 0000000..076377e --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_read.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "read.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "read@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_read.imageset/read.png b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_read.imageset/read.png new file mode 100644 index 0000000..0a0a1e7 Binary files /dev/null and b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_read.imageset/read.png differ diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_read.imageset/read@2x.png b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_read.imageset/read@2x.png new file mode 100644 index 0000000..1c04aee Binary files /dev/null and b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_read.imageset/read@2x.png differ diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_sending.imageset/Contents.json b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_sending.imageset/Contents.json new file mode 100644 index 0000000..baddde9 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_sending.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "sending.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "sending@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_sending.imageset/sending.png b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_sending.imageset/sending.png new file mode 100644 index 0000000..92ffab8 Binary files /dev/null and b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_sending.imageset/sending.png differ diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_sending.imageset/sending@2x.png b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_sending.imageset/sending@2x.png new file mode 100644 index 0000000..87e587f Binary files /dev/null and b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_sending.imageset/sending@2x.png differ diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_sent.imageset/Contents.json b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_sent.imageset/Contents.json new file mode 100644 index 0000000..548236d --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_sent.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "sent.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "sent@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_sent.imageset/sent.png b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_sent.imageset/sent.png new file mode 100644 index 0000000..cc03a2d Binary files /dev/null and b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_sent.imageset/sent.png differ diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_sent.imageset/sent@2x.png b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_sent.imageset/sent@2x.png new file mode 100644 index 0000000..7bc560d Binary files /dev/null and b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/status icons/status_sent.imageset/sent@2x.png differ diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/user.imageset/Contents.json b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/user.imageset/Contents.json new file mode 100644 index 0000000..04c42e3 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/user.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "user.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "user@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/user.imageset/user.png b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/user.imageset/user.png new file mode 100644 index 0000000..69812d1 Binary files /dev/null and b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/user.imageset/user.png differ diff --git a/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/user.imageset/user@2x.png b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/user.imageset/user@2x.png new file mode 100644 index 0000000..c862f10 Binary files /dev/null and b/Whatsapp-Swift/Whatsapp-Swift/Images.xcassets/user.imageset/user@2x.png differ diff --git a/Whatsapp-Swift/Whatsapp-Swift/Info.plist b/Whatsapp-Swift/Whatsapp-Swift/Info.plist new file mode 100644 index 0000000..2db94a6 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/Info.plist @@ -0,0 +1,42 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIcons + + CFBundleIcons~ipad + + CFBundleIdentifier + com.hummingbird.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + Main + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + + diff --git a/Whatsapp-Swift/Whatsapp-Swift/Inputbar.swift b/Whatsapp-Swift/Whatsapp-Swift/Inputbar.swift new file mode 100644 index 0000000..7947a6d --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/Inputbar.swift @@ -0,0 +1,194 @@ +// +// Inputbar.swift +// Whatsapp +// +// Created by Rafael Castro on 7/11/15. / Swift Version by Breno Oliveira on 02/18/16. +// Copyright (c) 2015 HummingBird. All rights reserved. +// + +import UIKit + +let RIGHT_BUTTON_SIZE:CGFloat = 60 +let LEFT_BUTTON_SIZE:CGFloat = 45 + +@objc protocol InputbarDelegate:NSObjectProtocol { + func inputbarDidPressRightButton(inputbar:Inputbar) + func inputbarDidPressLeftButton(inputbar:Inputbar) + + optional func inputbarDidChangeHeight(newHeight:CGFloat) + optional func inputbarDidBecomeFirstResponder(inputbar:Inputbar) +} + +class Inputbar: UIToolbar, HPGrowingTextViewDelegate { + + var inputDelegate:InputbarDelegate! + + var textView:HPGrowingTextView! + var rightButton:UIButton! + var leftButton:UIButton! + + var placeholder:String! { + didSet { + self.textView.placeholder = self.placeholder + } + } + var leftButtonImage:UIImage! { + didSet { + self.leftButton?.setImage(self.leftButtonImage, forState:.Normal) + } + } + var rightButtonTextColor:UIColor! { + didSet { + self.rightButton?.setTitleColor(self.rightButtonTextColor, forState:.Normal) + } + } + var rightButtonText:String! { + didSet { + self.rightButton?.setTitle(self.rightButtonText, forState:.Normal) + } + } + + + var text:String { + return self.textView.text + } + + override init(frame: CGRect) { + super.init(frame: frame) + + self.addContent() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + + self.addContent() + } + + func addContent() { + self.addTextView() + self.addRightButton() + self.addLeftButton() + + self.autoresizingMask = [.FlexibleWidth, .FlexibleTopMargin] + } + + func addTextView() { + let size = self.frame.size + self.textView = HPGrowingTextView(frame:CGRectMake(LEFT_BUTTON_SIZE, + 5, + size.width - LEFT_BUTTON_SIZE - RIGHT_BUTTON_SIZE, + size.height)) + self.textView.isScrollable = false + self.textView.contentInset = UIEdgeInsetsMake(0, 5, 0, 5) + + self.textView.minNumberOfLines = 1 + self.textView.maxNumberOfLines = 6 + // you can also set the maximum height in points with maxHeight + // self.textView.maxHeight = 200; + self.textView.returnKeyType = .Go + self.textView.font = UIFont.systemFontOfSize(15) + self.textView.delegate = self + self.textView.internalTextView.scrollIndicatorInsets = UIEdgeInsetsMake(5, 0, 5, 0) + self.textView.backgroundColor = UIColor.whiteColor() + self.textView.placeholder = self.placeholder + + //self.textView.autocapitalizationType = .Sentences + self.textView.keyboardType = .Default + self.textView.returnKeyType = .Default + self.textView.enablesReturnKeyAutomatically = true + //self.textView.scrollIndicatorInsets = UIEdgeInsetsMake(0, -1, 0, 1) + //self.textView.textContainerInset = UIEdgeInsetsMake(8, 4, 8, 0) + self.textView.layer.cornerRadius = 5 + self.textView.layer.borderWidth = 0.5 + self.textView.layer.borderColor = UIColor(red:200/255 ,green:200/255, blue:205/255, alpha:1).CGColor + + self.textView.autoresizingMask = .FlexibleWidth; + + // view hierachy + self.addSubview(self.textView) + } + + func addRightButton() { + let size = self.frame.size + + self.rightButton = UIButton() + self.rightButton.frame = CGRectMake(size.width - RIGHT_BUTTON_SIZE, 0, RIGHT_BUTTON_SIZE, size.height) + self.rightButton.autoresizingMask = [.FlexibleTopMargin, .FlexibleLeftMargin] + self.rightButton.setTitleColor(UIColor.blueColor(), forState:.Normal) + self.rightButton.setTitleColor(UIColor.lightGrayColor(), forState:.Selected) + self.rightButton.setTitle("Done", forState:.Normal) + self.rightButton.titleLabel!.font = UIFont(name:"Helvetica", size:15) + + self.rightButton.addTarget(self, action:"didPressRightButton:", forControlEvents:.TouchUpInside) + + self.addSubview(self.rightButton) + + self.rightButton.selected = true + } + + func addLeftButton() { + let size = self.frame.size + + self.leftButton = UIButton() + self.leftButton.frame = CGRectMake(0, 0, LEFT_BUTTON_SIZE, size.height) + self.leftButton.autoresizingMask = [.FlexibleTopMargin, .FlexibleRightMargin] + self.leftButton.setImage(self.leftButtonImage, forState:.Normal) + + self.leftButton.addTarget(self, action:"didPressLeftButton:", forControlEvents:.TouchUpInside) + + self.addSubview(self.leftButton) + } + + func inputResignFirstResponder() { + self.textView.resignFirstResponder() + } + + + // MARK - Delegate + + func didPressRightButton(sender:UIButton) { + if self.rightButton.selected { + return + } + + self.inputDelegate?.inputbarDidPressRightButton(self) + self.textView.text = "" + } + + func didPressLeftButton(sender:UIButton) { + self.inputDelegate?.inputbarDidPressLeftButton(self) + } + + // MARK - HPGrowingTextView + + func growingTextView(growingTextView: HPGrowingTextView!, willChangeHeight height: Float) { + let diff = growingTextView.frame.size.height - CGFloat(height) + + var r = self.frame + r.size.height -= diff + r.origin.y += diff + self.frame = r + + if self.inputDelegate != nil && self.inputDelegate!.respondsToSelector("inputbarDidChangeHeight:") { + self.inputDelegate.inputbarDidChangeHeight!(self.frame.size.height) + } + } + + func growingTextViewDidBeginEditing(growingTextView: HPGrowingTextView!) { + if self.inputDelegate != nil && self.inputDelegate!.respondsToSelector("inputbarDidBecomeFirstResponder:") { + self.inputDelegate.inputbarDidBecomeFirstResponder!(self) + } + } + + + func growingTextViewDidChange(growingTextView: HPGrowingTextView!) { + let text = growingTextView.text.stringByReplacingOccurrencesOfString(" ", withString:"") + if text.characters.count == 0 { + self.rightButton.selected = true + } + else { + self.rightButton.selected = false + } + } +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/LaunchScreen.storyboard b/Whatsapp-Swift/Whatsapp-Swift/LaunchScreen.storyboard new file mode 100644 index 0000000..25dcb3b --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/LaunchScreen.storyboard @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Whatsapp-Swift/Whatsapp-Swift/LocalStorage.swift b/Whatsapp-Swift/Whatsapp-Swift/LocalStorage.swift new file mode 100644 index 0000000..12c5c01 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/LocalStorage.swift @@ -0,0 +1,60 @@ +// +// LocalStorage.swift +// Whatsapp +// +// Created by Rafael Castro on 7/24/15. / Swift Version by Breno Oliveira on 02/18/16. +// Copyright (c) 2015 HummingBird. All rights reserved. +// + +import UIKit + +class LocalStorage { + private var mapChatToMessages:[String:[Message]] = [String:[Message]]() + + func storeMessage(message:Message) { + self.storeMessages([message]) + } + + func storeMessages(messages:[Message]) { + if messages.count == 0 { + return + } + + let message = messages[0] + let chatId = message.chatId + + var array = self.queryMessagesForChatId(chatId) + if array != nil { + array!.appendContentsOf(messages) + } + else { + array = messages + } + + self.mapChatToMessages[chatId] = array + } + + func queryMessagesForChatId(chatId:String) -> [Message]? + { + return self.mapChatToMessages[chatId] + } + + func storeChat(chat:Chat) { + //TODO + } + + func storeChats(chats:[Chat]) { + //TODO + } + + func storeContact(contact:Contact) { + //TODO + } + + func storeContacts(contacts:[Contact]) { + //TODO + } + + static let sharedInstance = LocalStorage() + private init() {} +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/Message.swift b/Whatsapp-Swift/Whatsapp-Swift/Message.swift new file mode 100644 index 0000000..f584a1b --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/Message.swift @@ -0,0 +1,59 @@ +// +// Message.swift +// Whatsapp +// +// Created by Rafael Castro on 6/16/15. / Swift Version by Breno Oliveira on 02/18/16. +// Copyright (c) 2015 HummingBird. All rights reserved. +// + +import UIKit + +enum MessageStatus { + case Sending + case Sent + case Received + case Read + case Failed +} + +enum MessageSender { + case Myself + case Someone +} + +class Message:NSObject { + var sender:MessageSender = .Myself + var status:MessageStatus = .Sending + var identifier:String = "" + var chatId:String = "" + var text:String = "" + var date:NSDate = NSDate() + var height:CGFloat = 44 + + func messageFromDictionary(dictionary:NSDictionary) -> Message { + let message = Message() + message.text = dictionary["text"] as! String + message.identifier = dictionary["messageId"] as! String + message.status = dictionary["status"] as! MessageStatus + + let dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS" + + //Date in UTC + let inputTimeZone = NSTimeZone(abbreviation: "UTC") + let inputDateFormatter = NSDateFormatter() + inputDateFormatter.timeZone = inputTimeZone + inputDateFormatter.dateFormat = dateFormat + let date = inputDateFormatter.dateFromString(dictionary["sent"] as! String) + + //Convert time in UTC to Local TimeZone + let outputTimeZone = NSTimeZone.localTimeZone() + let outputDateFormatter = NSDateFormatter() + outputDateFormatter.timeZone = outputTimeZone + outputDateFormatter.dateFormat = dateFormat + let outputString = outputDateFormatter.stringFromDate(date!) + + message.date = outputDateFormatter.dateFromString(outputString)! + + return message + } +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/MessageCell.swift b/Whatsapp-Swift/Whatsapp-Swift/MessageCell.swift new file mode 100644 index 0000000..ff952d4 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/MessageCell.swift @@ -0,0 +1,285 @@ +// +// MessageCell.swift +// Whatsapp +// +// Created by Rafael Castro on 7/23/15. / Swift Version by Breno Oliveira on 02/18/16. +// Copyright (c) 2015 HummingBird. All rights reserved. +// + +import UIKit + + +class MessageCell:UITableViewCell { + + private var timeLabel:UILabel! + private var textView:UITextView! + private var bubbleImage:UIImageView! + private var statusIcon:UIImageView! + + var resendButton:UIButton! + + var message:Message! { + didSet { + self.buildCell() + message.height = self.height + } + } + + + var height:CGFloat! { + get { + return self.bubbleImage.frame.size.height + } + } + + + func updateMessageStatus() { + self.buildCell() + //Animate Transition + self.statusIcon.alpha = 0 + UIView.animateWithDuration(0.5, animations:{ + self.statusIcon.alpha = 1 + }) + } + + // MARK - + + override init(style: UITableViewCellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + self.commonInit() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + + self.commonInit() + } + + func commonInit() { + self.backgroundColor = UIColor.clearColor() + self.contentView.backgroundColor = UIColor.clearColor() + self.selectionStyle = .None + self.accessoryType = .None + + self.textView = UITextView() + self.bubbleImage = UIImageView() + self.timeLabel = UILabel() + self.statusIcon = UIImageView() + self.resendButton = UIButton() + self.resendButton.hidden = true + + self.contentView.addSubview(self.bubbleImage) + self.contentView.addSubview(self.textView) + self.contentView.addSubview(self.timeLabel) + self.contentView.addSubview(self.statusIcon) + self.contentView.addSubview(self.resendButton) + } + override func prepareForReuse() { + super.prepareForReuse() + + self.textView.text = "" + self.timeLabel.text = "" + self.statusIcon.image = nil + self.bubbleImage.image = nil + self.resendButton.hidden = true + } + + func buildCell() { + self.setTextView() + self.setTimeLabel() + self.setBubble() + + self.addStatusIcon() + self.setStatusIcon() + + self.setFailedButton() + + self.setNeedsLayout() + } + + // MART - TextView + + func setTextView() { + let maxWitdh = 0.7*self.contentView.frame.size.width + self.textView.frame = CGRectMake(0, 0, maxWitdh, CGFloat(MAXFLOAT)) + self.textView.font = UIFont(name:"Helvetica", size:17) + self.textView.backgroundColor = UIColor.clearColor() + self.textView.userInteractionEnabled = false + + self.textView.text = self.message.text + self.textView.sizeToFit() + + var textViewX:CGFloat + var textViewY:CGFloat + let textViewW = self.textView.frame.size.width + let textViewH = self.textView.frame.size.height + + var autoresizing:UIViewAutoresizing + + self.autoresizingMask = [.FlexibleWidth, .FlexibleHeight] + + if self.message.sender == .Myself { + textViewX = self.contentView.frame.size.width - textViewW - 20 + textViewY = -3 + autoresizing = .FlexibleLeftMargin + textViewX -= self.isSingleLineCase() ? 65 : 0 + textViewX -= self.isStatusFailedCase() ? (self.failDelta()-15) : 0 + } + else { + textViewX = 20 + textViewY = -1 + autoresizing = .FlexibleRightMargin; + } + + self.textView.autoresizingMask = autoresizing + self.textView.frame = CGRectMake(textViewX, textViewY, textViewW, textViewH) + } + + // MARK - TimeLabel + + func setTimeLabel() { + self.timeLabel.frame = CGRectMake(0, 0, 52, 14) + self.timeLabel.textColor = UIColor.lightGrayColor() + self.timeLabel.font = UIFont(name:"Helvetica", size:12) + self.timeLabel.userInteractionEnabled = false + self.timeLabel.alpha = 0.7 + self.timeLabel.textAlignment = .Right + + //Set Text to Label + let df = NSDateFormatter() + df.timeStyle = .ShortStyle + df.dateStyle = .NoStyle + df.doesRelativeDateFormatting = true + self.timeLabel.text = df.stringFromDate(message.date) + + //Set position + var timeX:CGFloat + var timeY = self.textView.frame.size.height - 10 + + if self.message.sender == .Myself + { + timeX = self.textView.frame.origin.x + self.textView.frame.size.width - self.timeLabel.frame.size.width - 20 + } + else + { + timeX = max(self.textView.frame.origin.x + self.textView.frame.size.width - self.timeLabel.frame.size.width, + self.textView.frame.origin.x) + } + + if self.isSingleLineCase() { + timeX = self.textView.frame.origin.x + self.textView.frame.size.width - 5 + timeY -= 10 + } + + self.timeLabel.frame = CGRectMake(timeX, timeY, self.timeLabel.frame.size.width, self.timeLabel.frame.size.height); + self.timeLabel.autoresizingMask = self.textView.autoresizingMask + } + func isSingleLineCase() -> Bool { + let deltaX:CGFloat = self.message.sender == .Myself ? 65.0 : 44 + + let textViewHeight = self.textView.frame.size.height + let textViewWidth = self.textView.frame.size.width + let viewWidth = self.contentView.frame.size.width + + //Single Line Case + return (textViewHeight <= 45 && textViewWidth + deltaX <= 0.8*viewWidth) ? true : false + } + + // MARK - Bubble + + func setBubble() { + //Margins to Bubble + let marginLeft:CGFloat = 5 + let marginRight:CGFloat = 2 + + //Bubble positions + var bubbleX:CGFloat + let bubbleY:CGFloat = 0 + var bubbleWidth:CGFloat + let bubbleHeight:CGFloat = min(self.textView.frame.size.height + 8, self.timeLabel.frame.origin.y + self.timeLabel.frame.size.height + 6) + + if (self.message.sender == .Myself) { + + bubbleX = min(self.textView.frame.origin.x - marginLeft, self.timeLabel.frame.origin.x - 2*marginLeft) + + self.bubbleImage.image = self.imageNamed("bubbleMine").stretchableImageWithLeftCapWidth(15, topCapHeight:14) + + + bubbleWidth = self.contentView.frame.size.width - bubbleX - marginRight + bubbleWidth -= self.isStatusFailedCase() ? self.failDelta() : 0 + } + else + { + bubbleX = marginRight; + + self.bubbleImage.image = self.imageNamed("bubbleSomeone").stretchableImageWithLeftCapWidth(21, topCapHeight:14) + + bubbleWidth = max(self.textView.frame.origin.x + self.textView.frame.size.width + marginLeft, + self.timeLabel.frame.origin.x + self.timeLabel.frame.size.width + 2*marginLeft) + } + + self.bubbleImage.frame = CGRectMake(bubbleX, bubbleY, bubbleWidth, bubbleHeight) + self.bubbleImage.autoresizingMask = self.textView.autoresizingMask + } + + // MARK - StatusIcon + + func addStatusIcon() { + let timeFrame = self.timeLabel.frame + var statusFrame = CGRectMake(0, 0, 15, 14) + statusFrame.origin.x = timeFrame.origin.x + timeFrame.size.width + 5 + statusFrame.origin.y = timeFrame.origin.y + self.statusIcon.frame = statusFrame + self.statusIcon.contentMode = .Left + self.statusIcon.autoresizingMask = self.textView.autoresizingMask + } + func setStatusIcon() { + switch self.message.status { + case .Sending: + self.statusIcon.image = self.imageNamed("status_sending") + case .Sent: + self.statusIcon.image = self.imageNamed("status_sent") + case .Received: + self.statusIcon.image = self.imageNamed("status_notified") + case .Read: + self.statusIcon.image = self.imageNamed("status_read") + case .Failed: + self.statusIcon.image = nil + } + + self.statusIcon.hidden = self.message.sender == .Someone + } + + // MARK - Failed Case + + // + // This delta is how much TextView + // and Bubble should shit left + // + func failDelta() -> CGFloat { + return 60 + } + + func isStatusFailedCase() -> Bool { + return self.message.status == .Failed + } + + func setFailedButton() { + let bSize:CGFloat = 22; + let frame = CGRectMake(self.contentView.frame.size.width - bSize - self.failDelta()/2 + 5, + (self.contentView.frame.size.height - bSize)/2, + bSize, + bSize); + + self.resendButton.frame = frame + self.resendButton.hidden = !self.isStatusFailedCase() + self.resendButton.setImage(self.imageNamed("status_failed"), forState:.Normal) + } + + // MARK - UIImage Helper + + func imageNamed(imageName:String) -> UIImage { + return UIImage(named:imageName)! + } +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/MessageController.swift b/Whatsapp-Swift/Whatsapp-Swift/MessageController.swift new file mode 100644 index 0000000..ae7694c --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/MessageController.swift @@ -0,0 +1,205 @@ +// +// MessageController.swift +// Whatsapp +// +// Created by Rafael Castro on 7/23/15. / Swift Version by Breno Oliveira on 02/18/16. +// Copyright (c) 2015 HummingBird. All rights reserved. +// + +class MessageController:UIViewController, InputbarDelegate, MessageGatewayDelegate, UITableViewDataSource, UITableViewDelegate { + + @IBOutlet weak var tableView:UITableView! + @IBOutlet weak var inputbar:Inputbar! + + var chat:Chat! { + didSet { + self.title = self.chat.contact.name + } + } + + private var tableArray:TableArray! + private var gateway:MessageGateway! + + override func viewDidLoad() { + super.viewDidLoad() + + self.setInputbar() + self.setTableView() + self.setGateway() + } + + override func viewDidAppear(animated:Bool) { + super.viewDidAppear(animated) + + self.view.keyboardTriggerOffset = inputbar.frame.size.height + self.view.addKeyboardPanningWithActionHandler() {[unowned self](keyboardFrameInView:CGRect, opening:Bool, closing:Bool) in + /* + self.view.removeKeyboardControl() + */ + + var toolBarFrame = self.inputbar.frame + toolBarFrame.origin.y = keyboardFrameInView.origin.y - toolBarFrame.size.height + self.inputbar.frame = toolBarFrame + + var tableViewFrame = self.tableView.frame + tableViewFrame.size.height = toolBarFrame.origin.y - 64 + self.tableView.frame = tableViewFrame + + self.tableViewScrollToBottomAnimated(false) + } + } + + override func viewDidDisappear(animated:Bool) { + super.viewDidDisappear(animated) + self.view.endEditing(true) + self.view.removeKeyboardControl() + self.gateway.dismiss() + } + + override func viewWillDisappear(animated:Bool) { + self.chat.lastMessage = self.tableArray!.lastObject() + } + + // MARK - + + func setInputbar() { + self.inputbar.placeholder = nil + self.inputbar.inputDelegate = self + self.inputbar.leftButtonImage = UIImage(named:"share") + self.inputbar.rightButtonText = "Send" + self.inputbar.rightButtonTextColor = UIColor(red:0, green:124/255, blue:1, alpha:1) + } + + func setTableView() { + self.tableArray = TableArray() + self.tableView.delegate = self + self.tableView.dataSource = self + self.tableView.tableFooterView = UIView(frame:CGRectMake(0, 0, self.view.frame.size.width, 10)) + self.tableView.separatorStyle = .None + self.tableView.backgroundColor = UIColor.clearColor() + + self.tableView.registerClass(MessageCell.self, forCellReuseIdentifier:"MessageCell") + } + + func setGateway() { + self.gateway = MessageGateway.sharedInstance + self.gateway.delegate = self + self.gateway.chat = self.chat + self.gateway.loadOldMessages() + } + + // MARK - Actions + + @IBAction func userDidTapScreen(sender:AnyObject) { + self.inputbar.inputResignFirstResponder() + } + + // MARK - TableViewDataSource + + func numberOfSectionsInTableView(tableView: UITableView) -> Int { + return self.tableArray.numberOfSections + } + + func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.tableArray.numberOfMessagesInSection(section) + } + + func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCellWithIdentifier("MessageCell") as! MessageCell + cell.message = self.tableArray.objectAtIndexPath(indexPath) + return cell; + } + + // MARK - UITableViewDelegate + + func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { + let message = self.tableArray.objectAtIndexPath(indexPath) + return message.height + } + + func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return 40 + } + + func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return self.tableArray.titleForSection(section) + } + + func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let frame = CGRectMake(0, 0, tableView.frame.size.width, 40) + + let view = UIView(frame:frame) + view.backgroundColor = UIColor.clearColor() + view.autoresizingMask = .FlexibleWidth + + let label = UILabel() + label.text = self.tableView(tableView, titleForHeaderInSection: section) + label.textAlignment = .Center + label.font = UIFont(name:"Helvetica", size:20) + label.sizeToFit() + label.center = view.center + label.font = UIFont(name:"Helvetica", size:13) + label.backgroundColor = UIColor(red:207/255, green:220/255, blue:252/255, alpha:1) + label.layer.cornerRadius = 10 + label.layer.masksToBounds = true + label.autoresizingMask = .None + view.addSubview(label) + + return view + } + + func tableViewScrollToBottomAnimated(animated:Bool) { + let numberOfSections = self.tableArray.numberOfSections + let numberOfRows = self.tableArray.numberOfMessagesInSection(numberOfSections-1) + if numberOfRows > 0 { + self.tableView.scrollToRowAtIndexPath(self.tableArray.indexPathForLastMessage(), atScrollPosition:.Bottom, animated:animated) + } + } + + // MARK - InputbarDelegate + + func inputbarDidPressRightButton(inputbar:Inputbar) { + let message = Message() + message.text = inputbar.text + message.date = NSDate() + message.chatId = self.chat.identifier + + //Store Message in memory + self.tableArray.addObject(message) + + //Insert Message in UI + let indexPath = self.tableArray.indexPathForMessage(message) + self.tableView.beginUpdates() + if self.tableArray.numberOfMessagesInSection(indexPath.section) == 1 { + self.tableView.insertSections(NSIndexSet(index:indexPath.section), withRowAnimation:.None) + } + self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation:.Bottom) + self.tableView.endUpdates() + + self.tableView.scrollToRowAtIndexPath(self.tableArray.indexPathForLastMessage(), atScrollPosition:.Bottom, animated:true) + + //Send message to server + self.gateway.sendMessage(message) + } + func inputbarDidPressLeftButton(inputbar:Inputbar) { + let alertView = UIAlertView(title: "Left Button Pressed", message: nil, delegate: nil, cancelButtonTitle: "OK") + alertView.show() + } + func inputbarDidChangeHeight(newHeight:CGFloat) { + //Update DAKeyboardControl + self.view.keyboardTriggerOffset = newHeight + } + + // MARK - MessageGatewayDelegate + + func gatewayDidUpdateStatusForMessage(message:Message) { + let indexPath = self.tableArray.indexPathForMessage(message) + let cell = self.tableView.cellForRowAtIndexPath(indexPath) as! MessageCell + cell.updateMessageStatus() + } + + func gatewayDidReceiveMessages(array:[Message]) { + self.tableArray.addObjectsFromArray(array) + self.tableView.reloadData() + } +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/MessageGateway.swift b/Whatsapp-Swift/Whatsapp-Swift/MessageGateway.swift new file mode 100644 index 0000000..b105218 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/MessageGateway.swift @@ -0,0 +1,113 @@ +// +// MessageGateway.swift +// Whatsapp +// +// Created by Rafael Castro on 7/4/15. / Swift Version by Breno Oliveira on 02/18/16. +// Copyright (c) 2015 HummingBird. All rights reserved. +// + +protocol MessageGatewayDelegate:NSObjectProtocol { + func gatewayDidUpdateStatusForMessage(message:Message) + func gatewayDidReceiveMessages(array:[Message]) +} + + +class MessageGateway:NSObject { + var delegate:MessageGatewayDelegate! + var chat:Chat! + var messagesToSend:NSMutableArray = NSMutableArray() + + func loadOldMessages() { + let array = LocalStorage.sharedInstance.queryMessagesForChatId(self.chat.identifier) + if array == nil { + return + } + + self.delegate?.gatewayDidReceiveMessages(array!) + + let unreadMessages = self.queryUnreadMessagesInArray(array!) + self.updateStatusToReadInArray(unreadMessages) + } + + func updateStatusToReadInArray(unreadMessages:[Message]) { + var readIds = [String]() + for message in unreadMessages { + message.status = .Read + readIds.append(message.identifier) + } + self.chat.numberOfUnreadMessages = 0 + self.sendReadStatusToMessages(readIds) + } + + func queryUnreadMessagesInArray(array:[Message]) -> [Message] { + return array.filter({(message:Message) -> Bool in + return message.status == .Received + }) + } + + func news() { + + } + func dismiss() { + self.delegate = nil + } + + func fakeMessageUpdate(message:Message) { + self.performSelector("updateMessageStatus:", withObject:message, afterDelay:2) + } + + func updateMessageStatus(message:Message) { + + switch message.status { + case .Sending: + message.status = .Failed + case .Failed: + message.status = .Sent + case .Sent: + message.status = .Received + case .Received: + message.status = .Read + default: break + } + + if self.delegate != nil && self.delegate.respondsToSelector("gatewayDidUpdateStatusForMessage:") { + self.delegate!.gatewayDidUpdateStatusForMessage(message) + } + + // + // Remove this when connect to your server + // fake update message + // + if message.status != .Read { + self.fakeMessageUpdate(message) + } + } + + // MARK - Exchange data with API + + func sendMessage(message:Message) { + // + // Add here your code to send message to your server + // When you receive the response, you should update message status + // Now I'm just faking update message + // + LocalStorage.sharedInstance.storeMessage(message) + self.fakeMessageUpdate(message) + //TODO + } + func sendReadStatusToMessages(messageIds:[String]) { + if messageIds.count == 0 { + return + } + //TODO + } + func sendReceivedStatusToMessages(messageIds:[String]) { + if messageIds.count == 0 { + return + } + //TODO + } + + static let sharedInstance = MessageGateway() + private override init() {} +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-Swift/TableArray.swift b/Whatsapp-Swift/Whatsapp-Swift/TableArray.swift new file mode 100644 index 0000000..38765ed --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-Swift/TableArray.swift @@ -0,0 +1,162 @@ +// +// MessageArray.swift +// Whatsapp +// +// Created by Rafael Castro on 6/18/15. / Swift Version by Breno Oliveira on 02/18/16. +// Copyright (c) 2015 HummingBird. All rights reserved. +// + +import UIKit + +class TableArray { + + var mapTitleToMessages:[String:[Message]] = [String:[Message]]() + var orderedTitles:[String] = [String]() + var numberOfSections = 0 + var numberOfMessages = 0 + var formatter:NSDateFormatter! + + init() { + self.formatter = NSDateFormatter() + self.formatter.timeStyle = .NoStyle + self.formatter.dateStyle = .ShortStyle + self.formatter.doesRelativeDateFormatting = false + } + + func addObject(message:Message) { + return self.addMessage(message) + } + + func addObjectsFromArray(messages:[Message]) { + for message in messages { + self.addMessage(message) + } + } + func removeObject(message:Message) { + self.removeMessage(message) + } + func removeObjectsInArray(messages:[Message]) { + for message in messages { + self.removeMessage(message) + } + } + func removeAllObjects() { + self.mapTitleToMessages.removeAll() + self.orderedTitles.removeAll() + } + + func numberOfMessagesInSection(section:Int) -> Int { + if self.orderedTitles.count == 0 { + return 0 + } + let key = self.orderedTitles[section] + let array = self.mapTitleToMessages[key] + + return array!.count + } + + func titleForSection(section:Int) -> String { + let formatter = self.formatter.copy() as! NSDateFormatter + let key = self.orderedTitles[section] + let date = formatter.dateFromString(key) + + formatter.doesRelativeDateFormatting = true + return formatter.stringFromDate(date!) + } + + func objectAtIndexPath(indexPath:NSIndexPath) -> Message { + let key = self.orderedTitles[indexPath.section] + let array = self.mapTitleToMessages[key] + + return array![indexPath.row] + } + + func lastObject() -> Message { + let indexPath = self.indexPathForLastMessage() + + return self.objectAtIndexPath(indexPath) + } + + func indexPathForLastMessage() -> NSIndexPath { + let lastSection = self.numberOfSections-1 + let numberOfMessages = self.numberOfMessagesInSection(lastSection) + + return NSIndexPath(forRow:numberOfMessages-1, inSection:lastSection) + } + + func indexPathForMessage(message:Message) -> NSIndexPath { + let key = self.keyForMessage(message) + let section = self.orderedTitles.indexOf(key) + let row = self.mapTitleToMessages[key]!.indexOf({ (el) -> Bool in + return el == message + }) + + return NSIndexPath(forRow:row!, inSection:section!) + } + + // MARK - Helpers + + func addMessage(message:Message) { + let key = self.keyForMessage(message) + var array = self.mapTitleToMessages[key] + + if array == nil { + self.numberOfSections += 1; + array = [Message]() + } + + array?.append(message) + + + let sortedArray = array?.sort({ (m1, m2) -> Bool in + return m1.date == m2.date + }) + + self.mapTitleToMessages[key] = sortedArray + self.cacheTitles() + + self.numberOfMessages += 1 + } + func removeMessage(message:Message) { + let key = self.keyForMessage(message) + var array = self.mapTitleToMessages[key] + if array != nil { + + for (index, msg) in array!.enumerate() { + if msg == message { + array!.removeAtIndex(index) + return + } + } + + if array!.count == 0 { + self.numberOfSections -= 1 + self.mapTitleToMessages.removeValueForKey(key) + self.cacheTitles() + } + else { + self.mapTitleToMessages[key] = array + } + + self.numberOfMessages -= 1 + } + } + + func cacheTitles() { + let array = self.mapTitleToMessages.keys + + + let orderedArray = array.sort { (dateString1, dateString2) -> Bool in + let d1 = self.formatter.dateFromString(dateString1) + let d2 = self.formatter.dateFromString(dateString2) + + return d1 == d2 + } + + self.orderedTitles = orderedArray + } + + func keyForMessage(message:Message) -> String { + return self.formatter.stringFromDate(message.date) + } +} \ No newline at end of file diff --git a/Whatsapp-Swift/Whatsapp-SwiftTests/Info.plist b/Whatsapp-Swift/Whatsapp-SwiftTests/Info.plist new file mode 100644 index 0000000..ba72822 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-SwiftTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/Whatsapp-Swift/Whatsapp-SwiftTests/Whatsapp_SwiftTests.swift b/Whatsapp-Swift/Whatsapp-SwiftTests/Whatsapp_SwiftTests.swift new file mode 100644 index 0000000..169f884 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-SwiftTests/Whatsapp_SwiftTests.swift @@ -0,0 +1,36 @@ +// +// Whatsapp_SwiftTests.swift +// Whatsapp-SwiftTests +// +// Created by Breno Lima on 2/18/16. +// Copyright © 2016 Breno Lima. All rights reserved. +// + +import XCTest +@testable import Whatsapp_Swift + +class Whatsapp_SwiftTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measureBlock { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/Whatsapp-Swift/Whatsapp-SwiftUITests/Info.plist b/Whatsapp-Swift/Whatsapp-SwiftUITests/Info.plist new file mode 100644 index 0000000..ba72822 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-SwiftUITests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/Whatsapp-Swift/Whatsapp-SwiftUITests/Whatsapp_SwiftUITests.swift b/Whatsapp-Swift/Whatsapp-SwiftUITests/Whatsapp_SwiftUITests.swift new file mode 100644 index 0000000..96b01f9 --- /dev/null +++ b/Whatsapp-Swift/Whatsapp-SwiftUITests/Whatsapp_SwiftUITests.swift @@ -0,0 +1,36 @@ +// +// Whatsapp_SwiftUITests.swift +// Whatsapp-SwiftUITests +// +// Created by Breno Lima on 2/18/16. +// Copyright © 2016 Breno Lima. All rights reserved. +// + +import XCTest + +class Whatsapp_SwiftUITests: XCTestCase { + + override func setUp() { + super.setUp() + + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. + XCUIApplication().launch() + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testExample() { + // Use recording to get started writing UI tests. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + +}