From 76a00f273652d9938f30b53e6b7280afe0268ff4 Mon Sep 17 00:00:00 2001 From: Jim van Zummeren Date: Thu, 22 Nov 2018 21:21:16 +0100 Subject: [PATCH] Extension safety (#74) * Improve Markymark's way of URL opening by allowing customisation of the url opening behavior. - Support running Extensions and using extensionContext to open url's - Support i.e. opening urls in a internal Safari View Controller instead of the open url behavior --- Example/Podfile | 7 +- Example/Podfile.lock | 10 +- .../Base.lproj/MainInterface.storyboard | 46 ++++ Example/TodayExtension/Info.plist | 31 +++ .../TodayExtension/TodayViewController.swift | 28 +++ Example/markymark.xcodeproj/project.pbxproj | 215 ++++++++++++++++-- README.md | 139 +++++------ .../MarkDownTextView.swift | 12 +- .../URL Openers/DefaultURLOpener.swift | 26 +++ .../ExtensionContextURLOpener.swift | 21 ++ .../Block/CodeViewLayoutBlockBuilder.swift | 4 + .../Block/HeaderViewLayoutBlockBuilder.swift | 4 + .../ParagraphViewLayoutBlockBuilder.swift | 4 + .../Block/QuoteBlockLayoutBuilder.swift | 4 + ...tributedStringViewLayoutBlockBuilder.swift | 7 +- .../Views/AttributedInteractiveLabel.swift | 4 +- .../UIView/Views/ListItemView.swift | 12 +- markymark/Classes/Protocols/URLOpener.swift | 12 + ...MarkdownToViewConverterConfiguration.swift | 20 +- 19 files changed, 482 insertions(+), 124 deletions(-) create mode 100644 Example/TodayExtension/Base.lproj/MainInterface.storyboard create mode 100644 Example/TodayExtension/Info.plist create mode 100644 Example/TodayExtension/TodayViewController.swift create mode 100644 markymark/Classes/Default implementations/URL Openers/DefaultURLOpener.swift create mode 100644 markymark/Classes/Default implementations/URL Openers/ExtensionContextURLOpener.swift create mode 100644 markymark/Classes/Protocols/URLOpener.swift diff --git a/Example/Podfile b/Example/Podfile index f0000bd..c3d88a9 100644 --- a/Example/Podfile +++ b/Example/Podfile @@ -7,4 +7,9 @@ end target 'markymark_Tests' do inherit! :search_paths pod 'markymark', :path => '../' -end \ No newline at end of file +end + +target 'TodayExtension' do + inherit! :search_paths + pod 'markymark', :path => '../' +end diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 4d5f327..75a8c2b 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,16 +1,16 @@ PODS: - - markymark (7.1) + - markymark (8.0.0) DEPENDENCIES: - markymark (from `../`) EXTERNAL SOURCES: markymark: - :path: ../ + :path: "../" SPEC CHECKSUMS: - markymark: 2dbd0f000579b1992d0e6355d5cdd83b097d03f7 + markymark: 47a730450bc0c583f877a26ba3d77c3f7f930059 -PODFILE CHECKSUM: 702819c9a9352a2969fd49865aabe1f21e7c3404 +PODFILE CHECKSUM: b777cb29f5ff9935c02362ec42fa8a45e73dcf8f -COCOAPODS: 1.4.0 +COCOAPODS: 1.5.3 diff --git a/Example/TodayExtension/Base.lproj/MainInterface.storyboard b/Example/TodayExtension/Base.lproj/MainInterface.storyboard new file mode 100644 index 0000000..42e64cf --- /dev/null +++ b/Example/TodayExtension/Base.lproj/MainInterface.storyboard @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/TodayExtension/Info.plist b/Example/TodayExtension/Info.plist new file mode 100644 index 0000000..b601340 --- /dev/null +++ b/Example/TodayExtension/Info.plist @@ -0,0 +1,31 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + TodayExtension + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + XPC! + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + NSExtension + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.widget-extension + + + diff --git a/Example/TodayExtension/TodayViewController.swift b/Example/TodayExtension/TodayViewController.swift new file mode 100644 index 0000000..964229b --- /dev/null +++ b/Example/TodayExtension/TodayViewController.swift @@ -0,0 +1,28 @@ +// +// TodayViewController.swift +// TodayExtension +// +// Created by Jim van Zummeren on 21/11/2018. +// Copyright © 2018 CocoaPods. All rights reserved. +// + +import UIKit +import NotificationCenter +import markymark + +class TodayViewController: UIViewController, NCWidgetProviding { + + @IBOutlet var markDownTextView: MarkDownTextView? + + override func viewDidLoad() { + super.viewDidLoad() + markDownTextView?.text = """ + # Some header + A paragraph with a **link** to [Google](https://www.google.com) + + """ + + markDownTextView?.urlOpener = ExtensionContextURLOpener(extensionContext: self.extensionContext) + } +} + diff --git a/Example/markymark.xcodeproj/project.pbxproj b/Example/markymark.xcodeproj/project.pbxproj index 17a3291..2917be2 100644 --- a/Example/markymark.xcodeproj/project.pbxproj +++ b/Example/markymark.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 07614EA220B5565F00917A0C /* AdvancedMarkdownViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07614EA120B5565F00917A0C /* AdvancedMarkdownViewController.swift */; }; 07EC79F51CF3A19D004239CB /* ListRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07EC79F41CF3A19D004239CB /* ListRuleTests.swift */; }; 1E5D871F2AD8705B0A87B17C /* Pods_markymark_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E8357CFD26059D0E00EA777 /* Pods_markymark_Example.framework */; }; + 37393F83B3CB200FC35A7F67 /* Pods_TodayExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5770E41050B9B55F6767CB36 /* Pods_TodayExtension.framework */; }; 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; 607FACD81AFB9204008FA782 /* SimpleMarkDownViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* SimpleMarkDownViewController.swift */; }; 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; }; @@ -42,6 +43,10 @@ 9AD91A7A1CF7393A00BA3FD4 /* UnOrderedListTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AD91A791CF7393A00BA3FD4 /* UnOrderedListTypeTests.swift */; }; 9AD91A7E1CF851CF00BA3FD4 /* ListRuleIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AD91A7D1CF851CF00BA3FD4 /* ListRuleIntegrationTests.swift */; }; 9F346D66B4B31DFCC02A1889 /* Pods_markymark_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8C9BF4F2708091B2D40877E /* Pods_markymark_Tests.framework */; }; + F95E6FFA21A5E9FC006CA76E /* NotificationCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F95E6FF921A5E9FC006CA76E /* NotificationCenter.framework */; }; + F95E6FFD21A5E9FC006CA76E /* TodayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F95E6FFC21A5E9FC006CA76E /* TodayViewController.swift */; }; + F95E700021A5E9FC006CA76E /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F95E6FFE21A5E9FC006CA76E /* MainInterface.storyboard */; }; + F95E700421A5E9FC006CA76E /* TodayExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = F95E6FF821A5E9FC006CA76E /* TodayExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -52,13 +57,35 @@ remoteGlobalIDString = 607FACCF1AFB9204008FA782; remoteInfo = markymark; }; + F95E700221A5E9FC006CA76E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 607FACC81AFB9204008FA782 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F95E6FF721A5E9FC006CA76E; + remoteInfo = TodayExtension; + }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + F95E700821A5E9FC006CA76E /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + F95E700421A5E9FC006CA76E /* TodayExtension.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 07614EA120B5565F00917A0C /* AdvancedMarkdownViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedMarkdownViewController.swift; sourceTree = ""; }; 07EC79F41CF3A19D004239CB /* ListRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListRuleTests.swift; sourceTree = ""; }; 09F6D551E99F224FBB0ACEA1 /* Pods-markymark_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-markymark_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-markymark_Example/Pods-markymark_Example.debug.xcconfig"; sourceTree = ""; }; 518BCFAF9A99531082BDFBDE /* markymark.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = markymark.podspec; path = ../markymark.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 5770E41050B9B55F6767CB36 /* Pods_TodayExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TodayExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 5F43B9B7B5010DA326846718 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 607FACD01AFB9204008FA782 /* markymark_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = markymark_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -82,6 +109,7 @@ 629C42501CEB58D000DA57D8 /* markdown.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = markdown.txt; sourceTree = ""; }; 6E8357CFD26059D0E00EA777 /* Pods_markymark_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_markymark_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9704F30C7296E4A0EA9A0087 /* Pods-markymark_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-markymark_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-markymark_Example/Pods-markymark_Example.release.xcconfig"; sourceTree = ""; }; + 99E80ED3FF06693617B60997 /* Pods-TodayExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TodayExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-TodayExtension/Pods-TodayExtension.debug.xcconfig"; sourceTree = ""; }; 9A06FCF01CF72B4D0040251D /* AlphabeticListTypeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlphabeticListTypeTests.swift; sourceTree = ""; }; 9A8BE0A31CEE0416004593A0 /* BoldRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BoldRuleTests.swift; path = Block/BoldRuleTests.swift; sourceTree = ""; }; 9A8BE0A41CEE0416004593A0 /* ImageRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ImageRuleTests.swift; path = Block/ImageRuleTests.swift; sourceTree = ""; }; @@ -98,8 +126,14 @@ 9ACED21B5B719CEBD45DC4FF /* Pods-markymark_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-markymark_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-markymark_Tests/Pods-markymark_Tests.debug.xcconfig"; sourceTree = ""; }; 9AD91A791CF7393A00BA3FD4 /* UnOrderedListTypeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnOrderedListTypeTests.swift; sourceTree = ""; }; 9AD91A7D1CF851CF00BA3FD4 /* ListRuleIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListRuleIntegrationTests.swift; sourceTree = ""; }; + A91946E3427CFFC9F3CCB54F /* Pods-TodayExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TodayExtension.release.xcconfig"; path = "Pods/Target Support Files/Pods-TodayExtension/Pods-TodayExtension.release.xcconfig"; sourceTree = ""; }; D63514EC31AEF8C31E855D7D /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; F8C9BF4F2708091B2D40877E /* Pods_markymark_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_markymark_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F95E6FF821A5E9FC006CA76E /* TodayExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = TodayExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + F95E6FF921A5E9FC006CA76E /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; + F95E6FFC21A5E9FC006CA76E /* TodayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayViewController.swift; sourceTree = ""; }; + F95E6FFF21A5E9FC006CA76E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; + F95E700121A5E9FC006CA76E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F999758130B6D43C3A4FA9BF /* Pods-markymark_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-markymark_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-markymark_Tests/Pods-markymark_Tests.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -120,6 +154,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F95E6FF521A5E9FC006CA76E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F95E6FFA21A5E9FC006CA76E /* NotificationCenter.framework in Frameworks */, + 37393F83B3CB200FC35A7F67 /* Pods_TodayExtension.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -129,6 +172,7 @@ 607FACF51AFB993E008FA782 /* Podspec Metadata */, 607FACD21AFB9204008FA782 /* Example for markymark */, 607FACE81AFB9204008FA782 /* Tests */, + F95E6FFB21A5E9FC006CA76E /* TodayExtension */, 607FACD11AFB9204008FA782 /* Products */, 65D32CB691ED4A28AA22D2D8 /* Pods */, FD08777330AF1F43499E5156 /* Frameworks */, @@ -140,6 +184,7 @@ children = ( 607FACD01AFB9204008FA782 /* markymark_Example.app */, 607FACE51AFB9204008FA782 /* markymark_Tests.xctest */, + F95E6FF821A5E9FC006CA76E /* TodayExtension.appex */, ); name = Products; sourceTree = ""; @@ -269,6 +314,8 @@ 9704F30C7296E4A0EA9A0087 /* Pods-markymark_Example.release.xcconfig */, 9ACED21B5B719CEBD45DC4FF /* Pods-markymark_Tests.debug.xcconfig */, F999758130B6D43C3A4FA9BF /* Pods-markymark_Tests.release.xcconfig */, + 99E80ED3FF06693617B60997 /* Pods-TodayExtension.debug.xcconfig */, + A91946E3427CFFC9F3CCB54F /* Pods-TodayExtension.release.xcconfig */, ); name = Pods; sourceTree = ""; @@ -287,11 +334,23 @@ name = Inline; sourceTree = ""; }; + F95E6FFB21A5E9FC006CA76E /* TodayExtension */ = { + isa = PBXGroup; + children = ( + F95E6FFC21A5E9FC006CA76E /* TodayViewController.swift */, + F95E6FFE21A5E9FC006CA76E /* MainInterface.storyboard */, + F95E700121A5E9FC006CA76E /* Info.plist */, + ); + path = TodayExtension; + sourceTree = ""; + }; FD08777330AF1F43499E5156 /* Frameworks */ = { isa = PBXGroup; children = ( 6E8357CFD26059D0E00EA777 /* Pods_markymark_Example.framework */, F8C9BF4F2708091B2D40877E /* Pods_markymark_Tests.framework */, + F95E6FF921A5E9FC006CA76E /* NotificationCenter.framework */, + 5770E41050B9B55F6767CB36 /* Pods_TodayExtension.framework */, ); name = Frameworks; sourceTree = ""; @@ -308,11 +367,12 @@ 607FACCD1AFB9204008FA782 /* Frameworks */, 607FACCE1AFB9204008FA782 /* Resources */, F220BF3FD9625FD67595F183 /* [CP] Embed Pods Frameworks */, - 4536D5F2E1F7AC6E6B2E238E /* [CP] Copy Pods Resources */, + F95E700821A5E9FC006CA76E /* Embed App Extensions */, ); buildRules = ( ); dependencies = ( + F95E700321A5E9FC006CA76E /* PBXTargetDependency */, ); name = markymark_Example; productName = markymark; @@ -328,7 +388,6 @@ 607FACE21AFB9204008FA782 /* Frameworks */, 607FACE31AFB9204008FA782 /* Resources */, 029A2E5D6800056D702BE822 /* [CP] Embed Pods Frameworks */, - 7789EB477652F3880BD14E09 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -340,13 +399,31 @@ productReference = 607FACE51AFB9204008FA782 /* markymark_Tests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + F95E6FF721A5E9FC006CA76E /* TodayExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = F95E700721A5E9FC006CA76E /* Build configuration list for PBXNativeTarget "TodayExtension" */; + buildPhases = ( + 02B3E7BD0E47B9FC7388EEBB /* [CP] Check Pods Manifest.lock */, + F95E6FF421A5E9FC006CA76E /* Sources */, + F95E6FF521A5E9FC006CA76E /* Frameworks */, + F95E6FF621A5E9FC006CA76E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = TodayExtension; + productName = TodayExtension; + productReference = F95E6FF821A5E9FC006CA76E /* TodayExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 607FACC81AFB9204008FA782 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0720; + LastSwiftUpdateCheck = 1010; LastUpgradeCheck = 0900; ORGANIZATIONNAME = CocoaPods; TargetAttributes = { @@ -362,6 +439,11 @@ LastSwiftMigration = 0900; TestTargetID = 607FACCF1AFB9204008FA782; }; + F95E6FF721A5E9FC006CA76E = { + CreatedOnToolsVersion = 10.1; + DevelopmentTeam = 3B6J93GERH; + ProvisioningStyle = Automatic; + }; }; }; buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "markymark" */; @@ -379,6 +461,7 @@ targets = ( 607FACCF1AFB9204008FA782 /* markymark_Example */, 607FACE41AFB9204008FA782 /* markymark_Tests */, + F95E6FF721A5E9FC006CA76E /* TodayExtension */, ); }; /* End PBXProject section */ @@ -402,6 +485,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F95E6FF621A5E9FC006CA76E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F95E700021A5E9FC006CA76E /* MainInterface.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -430,7 +521,7 @@ ); inputPaths = ( "${SRCROOT}/Pods/Target Support Files/Pods-markymark_Tests/Pods-markymark_Tests-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/markymark/markymark.framework", + "${BUILT_PRODUCTS_DIR}/markymark-iOS8.3/markymark.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( @@ -441,34 +532,26 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-markymark_Tests/Pods-markymark_Tests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 4536D5F2E1F7AC6E6B2E238E /* [CP] Copy Pods Resources */ = { + 02B3E7BD0E47B9FC7388EEBB /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-markymark_Example/Pods-markymark_Example-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - 7789EB477652F3880BD14E09 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( + inputFileListPaths = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "[CP] Copy Pods Resources"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-TodayExtension-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-markymark_Tests/Pods-markymark_Tests-resources.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; D70756FE8E6C87A920EAB880 /* [CP] Check Pods Manifest.lock */ = { @@ -496,7 +579,8 @@ ); inputPaths = ( "${SRCROOT}/Pods/Target Support Files/Pods-markymark_Example/Pods-markymark_Example-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/markymark/markymark.framework", + "${BUILT_PRODUCTS_DIR}/markymark-iOS8.3/markymark.framework", + "${BUILT_PRODUCTS_DIR}/markymark-iOS12.1/markymark.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( @@ -553,6 +637,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F95E6FF421A5E9FC006CA76E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F95E6FFD21A5E9FC006CA76E /* TodayViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -561,6 +653,11 @@ target = 607FACCF1AFB9204008FA782 /* markymark_Example */; targetProxy = 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */; }; + F95E700321A5E9FC006CA76E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F95E6FF721A5E9FC006CA76E /* TodayExtension */; + targetProxy = F95E700221A5E9FC006CA76E /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -580,6 +677,14 @@ name = LaunchScreen.xib; sourceTree = ""; }; + F95E6FFE21A5E9FC006CA76E /* MainInterface.storyboard */ = { + isa = PBXVariantGroup; + children = ( + F95E6FFF21A5E9FC006CA76E /* Base */, + ); + name = MainInterface.storyboard; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ @@ -763,6 +868,63 @@ }; name = Release; }; + F95E700521A5E9FC006CA76E /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 99E80ED3FF06693617B60997 /* Pods-TodayExtension.debug.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 3B6J93GERH; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = TodayExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.markymark-Example.TodayExtension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_VERSION = 4.2; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + F95E700621A5E9FC006CA76E /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A91946E3427CFFC9F3CCB54F /* Pods-TodayExtension.release.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 3B6J93GERH; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = TodayExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.markymark-Example.TodayExtension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 4.2; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -793,6 +955,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + F95E700721A5E9FC006CA76E /* Build configuration list for PBXNativeTarget "TodayExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F95E700521A5E9FC006CA76E /* Debug */, + F95E700621A5E9FC006CA76E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 607FACC81AFB9204008FA782 /* Project object */; diff --git a/README.md b/README.md index ffb5b1f..4c9101e 100644 --- a/README.md +++ b/README.md @@ -25,26 +25,22 @@ pod "markymark" ``` ## Simple usage -
- View with default styling +### View with default styling ```swift let markDownView = MarkDownTextView() markDownView.text = "# Header\nParagraph" ``` -
- -
- View with modified styling +### View with modified styling Markymark has many styling options, please check the examples in the styling section of this readme. A simple example: ```swift let markDownView = MarkDownTextView() markDownView.styling.headingStyling.textColorsForLevels = [ - .orange, //H1 (i.e. # Title) - .black, //H2, ... (i.e. ## Subtitle, ### Sub subtitle) + .orange, //H1 (i.e. # Title) + .black, //H2, ... (i.e. ## Subtitle, ### Sub subtitle) ] markDownView.styling.linkStyling.textColor = .blue @@ -52,14 +48,10 @@ markDownView.styling.paragraphStyling.baseFont = .systemFont(ofSize: 14) markDownView.text = "# Header\nParagraph" ``` -
## Supported tags in the Default Flavor Note: Different tags can be supported by either extending the ContentfulFlavor (default) or by implementing a class that comforms to `Flavor` and implement the required `Rule`'s -
- Tags - ``` Headings # H1 @@ -71,11 +63,11 @@ Headings Lists - item - - item + - item * item - * item + * item + item - + item + + item a. item b. item 1. item @@ -99,22 +91,15 @@ Code \```code``` ``` -
### Customizing default style - -
- Default Styling instance +Default Styling instance ```swift var styling = DefaultStyling() ``` -
- -
- Paragraphs (regular text) - +#### Paragraphs (regular text) Markdown example: `Some text` ```swift @@ -126,41 +111,36 @@ styling.paragraphStyling.isBold = false styling.paragraphStyling.isItalic = false styling.paragraphStyling.textAlignment = .left ``` -
-
- Headings +#### Headings Markdown example: `# Title` or `## Subtitle` etc. ```swift styling.headingStyling.fontsForLevels = [ - UIFont.boldSystemFontOfSize(24), //H1 - UIFont.systemFontOfSize(18), //H2 - UIFont.systemFontOfSize(16) //H3, ... (last item will be next levels as well) + UIFont.boldSystemFontOfSize(24), //H1 + UIFont.systemFontOfSize(18), //H2 + UIFont.systemFontOfSize(16) //H3, ... (last item will be next levels as well) ] styling.headingStyling.colorsForLevels = [ - .red, //H1 - .black, //H2, ... (last item will be next levels as well) + .red, //H1 + .black, //H2, ... (last item will be next levels as well) ] // Margins styling.headingStyling.contentInsetsForLevels = [ - UIEdgeInsets(top: 5, left: 0, bottom: 15, right: 10), // H1 - UIEdgeInsets(top: 5, left: 0, bottom: 5, right: 10) //H2, ... (last item will be next levels as well) + UIEdgeInsets(top: 5, left: 0, bottom: 15, right: 10), // H1 + UIEdgeInsets(top: 5, left: 0, bottom: 5, right: 10) //H2, ... (last item will be next levels as well) ] - + styling.headingStyling.isBold = false styling.headingStyling.isItalic = false styling.headingStyling.isUnderlined = false styling.headingStyling.textAlignment = .left ``` -
- -
- Link Styling +#### linkStyling Markdown Example `[Google](http://www.google.com)` ```swift @@ -171,33 +151,30 @@ styling.linkStyling.isBold = false styling.linkStyling.isItalic = false styling.linkStyling.isUnderlined = true ``` -
- - -
- List Styling +#### List styling Markdown Example: ``` - List item 1 - List item 2 - - Nested List item +- Nested List item ``` + ```swift // By default a font will be used with the bullet character `•`. Use the follow properties to configure it's size and color: styling.listStyling.bulletFont = .systemFont(ofSize: 14) styling.listStyling.bulletColor = .black - + // Bullets can also be images for more complex styling. When setting images, bullet font and color won't be used anymore // Array of images used as bullet for each level of nested list items styling.listStyling.bulletImages = [ - UIImage(named: "circle"), - UIImage(named: "emptyCircle"), - UIImage(named: "line"), - UIImage(named: "square") + UIImage(named: "circle"), + UIImage(named: "emptyCircle"), + UIImage(named: "line"), + UIImage(named: "square") ] - + // Size of the images styling.listStyling.bulletViewSize = CGSize(width: 16, height: 16) @@ -206,14 +183,12 @@ styling.listStyling.contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: 10, r //Amount of space underneath each list item styling.listStyling.bottomListItemSpacing = 5 - + // Number of pixels to indent for each nested list level styling.listStyling.listIdentSpace = 15 - + styling.listStyling.textColor = .black ``` -
- Styling is also possible for: @@ -229,24 +204,23 @@ styling.codeBlockStyling styling.inlineCodeBlockStyling styling.quoteStyling ``` -_Please check the [DefaultStyling][1] class for more information_ -[1]: https://github.com/M2Mobi/Marky-Mark/blob/master/markymark/Classes/Styling/DefaultStyling.swift "DefaultStyling Class" +_Please check the `DefaultStyling` class for more information_ + ## Advanced usage Advanced usage is only needed for very specific cases. Making subsets of styling, making different styling combinations, supporting different Markdown rules (syntax) or modifying certain views after that have been generated. -
- Custom styling objects +### Custom styling objects ```swift struct CustomMarkyMarkStyling: Styling { - var headerStyling = CustomHeaderStyling() - var paragraphStyling = ParagraphStyling() - var linkStyling = ListStyling() - - var itemStylingRules: [ItemStyling] { - return [headerStyling, paragraphStyling, linkStyling] - } + var headerStyling = CustomHeaderStyling() + var paragraphStyling = ParagraphStyling() + var linkStyling = ListStyling() + + var itemStylingRules: [ItemStyling] { + return [headerStyling, paragraphStyling, linkStyling] + } } ``` @@ -257,7 +231,7 @@ Each styling rule can be applied to a markDownItem by comforming to `ItemStyling ``` public func isApplicableOn(_ markDownItem: MarkDownItem) -> Bool { - return markDownItem is HeaderMarkDownItem + return markDownItem is HeaderMarkDownItem } ``` @@ -269,11 +243,7 @@ You can inject your new styling object by passing it to the constructor of the ` MarkDownTextView(styling: CustomMarkyMarkStyling()) ``` -
- -
- Adding your own rules - +### Adding your own rules Adding a new rule requires three new classes of based on the following protocol: * `Rule` that can recoginizes the desired markdown syntax @@ -298,21 +268,32 @@ If needed you can also add a custom styling class to the default styling styling.addStyling(MyCustomStyling()) ``` -
-
- Converter hook - +### Converter hook The converter has a callback method which is called every time a `MarkDownItem` is converted to layout. ```swift converter.didConvertElement = { - markDownItem, view in - // Do something with markDownItem and / or view here + markDownItem, view in + // Do something with markDownItem and / or view here } ``` -
+### Changing link behavior + +By default Markymark opens URL's using `UIApplication.shared.open(url:)`. Markymark allows changing this behavior by passing a custom URLOpener, an object that comforms to the `URLOpener` protocol. + +```swift +let markDownView = MarkDownTextView() +markDownTextView?.urlOpener = MyCustomerURLOpener() +``` + +### Using Markymark in Extensions +Markymark also supports usage the a Today extension. By default tapping url's is not working, since Extensions don't have access to UIApplication.shared, in order to support links you can pass a different url opener to a MarkyDownTextView. See the Example project for a working example: + +```swift +markDownTextView?.urlOpener = ExtensionContextURLOpener(extensionContext: self.extensionContext) +``` ## Author diff --git a/markymark/Classes/Default implementations/MarkDownTextView.swift b/markymark/Classes/Default implementations/MarkDownTextView.swift index 671f5a7..02af28f 100644 --- a/markymark/Classes/Default implementations/MarkDownTextView.swift +++ b/markymark/Classes/Default implementations/MarkDownTextView.swift @@ -23,6 +23,13 @@ public class MarkDownTextView: UIView { render(withMarkdownText: text) } } + + public var urlOpener: URLOpener? { + didSet { + (viewConfiguration as? MarkDownAsViewViewConfiguration)?.urlOpener = urlOpener + render(withMarkdownText: text) + } + } fileprivate var markDownView: UIView? fileprivate var markDownItems: [MarkDownItem] = [] @@ -82,7 +89,8 @@ public class MarkDownTextView: UIView { } } -private struct MarkDownAsViewViewConfiguration: CanConfigureViews { +private class MarkDownAsViewViewConfiguration: CanConfigureViews { + var urlOpener: URLOpener? private weak var owner: MarkDownTextView? @@ -92,7 +100,7 @@ private struct MarkDownAsViewViewConfiguration: CanConfigureViews { func configureViewProperties() { guard let owner = owner else { return } - let configuration = MarkdownToViewConverterConfiguration(styling: owner.styling) + let configuration = MarkdownToViewConverterConfiguration(styling: owner.styling, urlOpener: urlOpener) let converter = MarkDownConverter(configuration: configuration) owner.markDownView = converter.convert(owner.markDownItems) diff --git a/markymark/Classes/Default implementations/URL Openers/DefaultURLOpener.swift b/markymark/Classes/Default implementations/URL Openers/DefaultURLOpener.swift new file mode 100644 index 0000000..5e41ef3 --- /dev/null +++ b/markymark/Classes/Default implementations/URL Openers/DefaultURLOpener.swift @@ -0,0 +1,26 @@ +// +// DefaultURLOpener.swift +// markymark-iOS12.1 +// +// Created by Jim van Zummeren on 21/11/2018. +// + +import Foundation + +public class DefaultURLOpener: URLOpener { + + private var sharedApplication: UIApplication? { + let sharedSelector = NSSelectorFromString("sharedApplication") + guard UIApplication.responds(to: sharedSelector) else { + assertionFailure("[Extensions cannot access Application]") + return nil + } + + let shared = UIApplication.perform(sharedSelector) + return shared?.takeUnretainedValue() as? UIApplication + } + + public func open(url: URL) { + _ = sharedApplication?.openURL(url) + } +} diff --git a/markymark/Classes/Default implementations/URL Openers/ExtensionContextURLOpener.swift b/markymark/Classes/Default implementations/URL Openers/ExtensionContextURLOpener.swift new file mode 100644 index 0000000..998da30 --- /dev/null +++ b/markymark/Classes/Default implementations/URL Openers/ExtensionContextURLOpener.swift @@ -0,0 +1,21 @@ +// +// ExtensionContextURLOpener.swift +// markymark-iOS12.1 +// +// Created by Jim van Zummeren on 21/11/2018. +// + +import Foundation + +public class ExtensionContextURLOpener: URLOpener { + + private let extensionContext: NSExtensionContext? + + public init(extensionContext: NSExtensionContext?) { + self.extensionContext = extensionContext + } + + public func open(url: URL) { + extensionContext?.open(url, completionHandler: nil) + } +} diff --git a/markymark/Classes/Layout Builders/UIView/Block Builders/Block/CodeViewLayoutBlockBuilder.swift b/markymark/Classes/Layout Builders/UIView/Block Builders/Block/CodeViewLayoutBlockBuilder.swift index e099c5f..3d40ceb 100644 --- a/markymark/Classes/Layout Builders/UIView/Block Builders/Block/CodeViewLayoutBlockBuilder.swift +++ b/markymark/Classes/Layout Builders/UIView/Block Builders/Block/CodeViewLayoutBlockBuilder.swift @@ -23,6 +23,10 @@ class CodeViewLayoutBlockBuilder : InlineAttributedStringViewLayoutBlockBuilder label.font = (styling as? BaseFontStylingRule)?.baseFont label.textColor = (styling as? TextColorStylingRule)?.textColor + if let urlOpener = urlOpener { + label.urlOpener = urlOpener + } + let spacing:UIEdgeInsets? = (styling as? ContentInsetStylingRule)?.contentInsets let containerView = ContainerView(view: label, spacing: spacing) diff --git a/markymark/Classes/Layout Builders/UIView/Block Builders/Block/HeaderViewLayoutBlockBuilder.swift b/markymark/Classes/Layout Builders/UIView/Block Builders/Block/HeaderViewLayoutBlockBuilder.swift index 097a118..af43165 100644 --- a/markymark/Classes/Layout Builders/UIView/Block Builders/Block/HeaderViewLayoutBlockBuilder.swift +++ b/markymark/Classes/Layout Builders/UIView/Block Builders/Block/HeaderViewLayoutBlockBuilder.swift @@ -21,6 +21,10 @@ class HeaderViewLayoutBlockBuilder: InlineAttributedStringViewLayoutBlockBuilder let label = AttributedInteractiveLabel() label.numberOfLines = 0 label.markDownAttributedString = attributedStringForMarkDownItem(markDownItem, styling: headerStyling ?? styling) + + if let urlOpener = urlOpener { + label.urlOpener = urlOpener + } let spacing: UIEdgeInsets? = (styling as? ContentInsetStylingRule)?.contentInsets return ContainerView(view: label, spacing: spacing) diff --git a/markymark/Classes/Layout Builders/UIView/Block Builders/Block/ParagraphViewLayoutBlockBuilder.swift b/markymark/Classes/Layout Builders/UIView/Block Builders/Block/ParagraphViewLayoutBlockBuilder.swift index d179694..32137f4 100644 --- a/markymark/Classes/Layout Builders/UIView/Block Builders/Block/ParagraphViewLayoutBlockBuilder.swift +++ b/markymark/Classes/Layout Builders/UIView/Block Builders/Block/ParagraphViewLayoutBlockBuilder.swift @@ -17,6 +17,10 @@ class ParagraphViewLayoutBlockBuilder: InlineAttributedStringViewLayoutBlockBuil let label = AttributedInteractiveLabel() label.markDownAttributedString = attributedStringForMarkDownItem(markDownItem, styling: styling) + if let urlOpener = urlOpener { + label.urlOpener = urlOpener + } + let spacing:UIEdgeInsets? = (styling as? ContentInsetStylingRule)?.contentInsets return ContainerView(view: label, spacing: spacing) } diff --git a/markymark/Classes/Layout Builders/UIView/Block Builders/Block/QuoteBlockLayoutBuilder.swift b/markymark/Classes/Layout Builders/UIView/Block Builders/Block/QuoteBlockLayoutBuilder.swift index c8b9b85..c9accc7 100644 --- a/markymark/Classes/Layout Builders/UIView/Block Builders/Block/QuoteBlockLayoutBuilder.swift +++ b/markymark/Classes/Layout Builders/UIView/Block Builders/Block/QuoteBlockLayoutBuilder.swift @@ -18,6 +18,10 @@ class QuoteBlockLayoutBuilder: InlineAttributedStringViewLayoutBlockBuilder { let label = AttributedInteractiveLabel() label.markDownAttributedString = attributedStringForMarkDownItem(markDownItem, styling: styling) + if let urlOpener = urlOpener { + label.urlOpener = urlOpener + } + let spacing:UIEdgeInsets? = (styling as? ContentInsetStylingRule)?.contentInsets return ContainerView(view: label, spacing: spacing) } diff --git a/markymark/Classes/Layout Builders/UIView/Block Builders/Inline/InlineAttributedStringViewLayoutBlockBuilder.swift b/markymark/Classes/Layout Builders/UIView/Block Builders/Inline/InlineAttributedStringViewLayoutBlockBuilder.swift index d893171..cb9c10f 100644 --- a/markymark/Classes/Layout Builders/UIView/Block Builders/Inline/InlineAttributedStringViewLayoutBlockBuilder.swift +++ b/markymark/Classes/Layout Builders/UIView/Block Builders/Inline/InlineAttributedStringViewLayoutBlockBuilder.swift @@ -7,10 +7,13 @@ import UIKit class InlineAttributedStringViewLayoutBlockBuilder: LayoutBlockBuilder { - fileprivate let converter : MarkDownConverter + internal let urlOpener: URLOpener? - required init(converter : MarkDownConverter) { + private let converter : MarkDownConverter + + required init(converter : MarkDownConverter, urlOpener: URLOpener? = nil) { self.converter = converter + self.urlOpener = urlOpener super.init() } diff --git a/markymark/Classes/Layout Builders/UIView/Views/AttributedInteractiveLabel.swift b/markymark/Classes/Layout Builders/UIView/Views/AttributedInteractiveLabel.swift index bf95f09..2ae928c 100644 --- a/markymark/Classes/Layout Builders/UIView/Views/AttributedInteractiveLabel.swift +++ b/markymark/Classes/Layout Builders/UIView/Views/AttributedInteractiveLabel.swift @@ -30,6 +30,8 @@ open class AttributedInteractiveLabel: UILabel { self.attributedText = mutableAttributedString } } + + public var urlOpener: URLOpener = DefaultURLOpener() public init(){ super.init(frame: CGRect()) @@ -51,7 +53,7 @@ open class AttributedInteractiveLabel: UILabel { let locationInView = tapGesture.location(in: view) if let url = getUrlAtLocationInView(locationInView) { - UIApplication.shared.openURL(url) + urlOpener.open(url: url) } } diff --git a/markymark/Classes/Layout Builders/UIView/Views/ListItemView.swift b/markymark/Classes/Layout Builders/UIView/Views/ListItemView.swift index 0af5752..319f6ee 100644 --- a/markymark/Classes/Layout Builders/UIView/Views/ListItemView.swift +++ b/markymark/Classes/Layout Builders/UIView/Views/ListItemView.swift @@ -21,8 +21,12 @@ class ListItemView: UIView { var styling:BulletStylingRule? - init(listMarkDownItem:ListMarkDownItem, styling: BulletStylingRule?, attributedText: NSAttributedString){ - + init( + listMarkDownItem: ListMarkDownItem, + styling: BulletStylingRule?, + attributedText: NSAttributedString, + urlOpener: URLOpener? = nil + ) { self.listMarkDownItem = listMarkDownItem self.styling = styling @@ -30,6 +34,10 @@ class ListItemView: UIView { label.markDownAttributedString = attributedText label.numberOfLines = 0 + + if let urlOpener = urlOpener { + label.urlOpener = urlOpener + } setUpLayout() } diff --git a/markymark/Classes/Protocols/URLOpener.swift b/markymark/Classes/Protocols/URLOpener.swift new file mode 100644 index 0000000..245b67e --- /dev/null +++ b/markymark/Classes/Protocols/URLOpener.swift @@ -0,0 +1,12 @@ +// +// URLOpener.swift +// markymark-iOS12.1 +// +// Created by Jim van Zummeren on 21/11/2018. +// + +import Foundation + +public protocol URLOpener { + func open(url: URL) +} diff --git a/markymark/Classes/Renderer/Configurations/MarkdownToViewConverterConfiguration.swift b/markymark/Classes/Renderer/Configurations/MarkdownToViewConverterConfiguration.swift index 6f7e7ed..a7adc93 100644 --- a/markymark/Classes/Renderer/Configurations/MarkdownToViewConverterConfiguration.swift +++ b/markymark/Classes/Renderer/Configurations/MarkdownToViewConverterConfiguration.swift @@ -10,24 +10,24 @@ import UIKit open class MarkdownToViewConverterConfiguration: MarkDownConverterConfiguration { - public override init(elementComposer: ElementComposer, styling : Styling) { + public init(elementComposer: ElementComposer, styling : Styling, urlOpener: URLOpener? = nil) { super.init(elementComposer: elementComposer, styling : styling) let converter = MarkDownConverter(configuration: MarkDownToInlineAttributedStringConverterConfiguration(styling : styling)) - addLayoutBlockBuilder(HeaderViewLayoutBlockBuilder(converter: converter)) - addLayoutBlockBuilder(ParagraphViewLayoutBlockBuilder(converter: converter)) - addLayoutBlockBuilder(ListViewLayoutBlockBuilder(converter:converter)) - addLayoutBlockBuilder(OrderedListViewLayoutBlockBuilder(converter:converter)) - addLayoutBlockBuilder(AlphabeticListViewLayoutBlockBuilder(converter:converter)) - addLayoutBlockBuilder(CodeViewLayoutBlockBuilder(converter:converter)) - addLayoutBlockBuilder(QuoteBlockLayoutBuilder(converter:converter)) + addLayoutBlockBuilder(HeaderViewLayoutBlockBuilder(converter: converter, urlOpener: urlOpener)) + addLayoutBlockBuilder(ParagraphViewLayoutBlockBuilder(converter: converter, urlOpener: urlOpener)) + addLayoutBlockBuilder(ListViewLayoutBlockBuilder(converter:converter, urlOpener: urlOpener)) + addLayoutBlockBuilder(OrderedListViewLayoutBlockBuilder(converter:converter, urlOpener: urlOpener)) + addLayoutBlockBuilder(AlphabeticListViewLayoutBlockBuilder(converter:converter, urlOpener: urlOpener)) + addLayoutBlockBuilder(CodeViewLayoutBlockBuilder(converter:converter, urlOpener: urlOpener)) + addLayoutBlockBuilder(QuoteBlockLayoutBuilder(converter:converter, urlOpener: urlOpener)) addLayoutBlockBuilder(HorizontalLineLayoutBlockBuilder()) addLayoutBlockBuilder(ImageViewLayoutBlockBuilder()) } - public convenience init(styling : Styling){ - self.init(elementComposer: ViewAppenderComposer(), styling : styling) + public convenience init(styling : Styling, urlOpener: URLOpener? = nil){ + self.init(elementComposer: ViewAppenderComposer(), styling: styling, urlOpener: urlOpener) } }