├── .gitignore ├── .swiftpm └── xcode │ ├── package.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ ├── ali.xcuserdatad │ └── xcschemes │ │ └── xcschememanagement.plist │ └── luca.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── Package.swift ├── README.md ├── Sources └── ImageWithActivityIndicator │ ├── ActivityIndicator.swift │ ├── ImageWithActivityIndicator.swift │ ├── ViewLoader.swift │ └── ViewLoaders.swift └── Tests ├── ImageWithActivityIndicatorTests ├── ImageWithActivityIndicatorTests.swift └── XCTestManifests.swift └── LinuxMain.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcuserdata/ali.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ImageWithActivityIndicator.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | ImageWithActivityIndicator 16 | 17 | primary 18 | 19 | 20 | ImageWithActivityIndicatorTests 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcuserdata/luca.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ImageWithActivityIndicator.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "ImageWithActivityIndicator", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "ImageWithActivityIndicator", 12 | targets: ["ImageWithActivityIndicator"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 21 | .target( 22 | name: "ImageWithActivityIndicator", 23 | dependencies: []), 24 | .testTarget( 25 | name: "ImageWithActivityIndicatorTests", 26 | dependencies: ["ImageWithActivityIndicator"]), 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ViewWithActivityIndicator 2 | 3 | 4 | 5 | `ViewWithActivityIndicator` is a SwiftUI view that download and display image from URL and displaying Activity Indicator while loading . 6 | 7 | # ScreenShots 8 | 9 | ![N|Solid](https://github.com/AliAdam/ViewWithActivityIndicatorDemo/blob/master/preview.gif?raw=true) 10 | 11 | Demo app [ViewWithActivityIndicatorDemo](https://github.com/blackwiz4rd/ViewWithActivityIndicatorDemo). 12 | 13 | ## Installation 14 | 15 | `ViewWithActivityIndicator` is a Swift Package and you can install it with Xcode 11: 16 | - Copy SSH `git@github.com:blackwiz4rd/ViewWithActivityIndicator.git` or HTTPS `https://github.com/blackwiz4rd/ViewWithActivityIndicator.git` URL from github; 17 | - Open **File/Swift Packages/Add Package Dependency...** in Xcode 11; 18 | - Paste the URL and follow steps. 19 | 20 | ## Usage 21 | 22 | `ViewWithActivityIndicator` must be initialized with a URL and optional placeholder image. 23 | 24 | ```swift 25 | let url = "" 26 | 27 | ViewWithActivityIndicator(imageURL: url) 28 | 29 | ViewWithActivityIndicator(imageURL: url,placeHolder: "icon") 30 | ``` 31 | 32 | Using in a view: 33 | 34 | ```swift 35 | import SwiftUI 36 | import ViewWithActivityIndicator 37 | 38 | struct ContentView : View { 39 | 40 | 41 | let loader: ViewLoader = ViewLoader(url: "https://picsum.photos/300") 42 | 43 | var body: some View { 44 | ViewWithActivityIndicator(placeHolder: "", showActivityIndicator: true, viewLoader: loader) { 45 | Image(uiImage: UIImage(data:self.loader.getData()) ?? UIImage()) 46 | } 47 | } 48 | } 49 | ``` 50 | 51 | Using in a list: 52 | 53 | ```swift 54 | import SwiftUI 55 | import ViewWithActivityIndicator 56 | 57 | struct ContentView : View { 58 | let urls: [String] 59 | let loader: ViewLoader = ViewLoader(url: "https://picsum.photos/300") 60 | 61 | var body: some View { 62 | List(urls, id: \.self) { url in 63 | HStack { 64 | ViewWithActivityIndicator(imageURL: url) 65 | .frame(width: 100.0, height: 100.0) 66 | Text("\(url)") 67 | } 68 | } 69 | } 70 | } 71 | ``` 72 | 73 | ViewLoaders allows to create multiple loaders given a String array: 74 | ```swift 75 | @available(iOS 13.0, *) 76 | public struct ViewLoaders { 77 | var loaders: [ViewLoader] = [] 78 | init(urls: [String]) { 79 | for url in urls { 80 | loaders.append(ViewLoader(url: url)) 81 | } 82 | } 83 | } 84 | ``` 85 | -------------------------------------------------------------------------------- /Sources/ImageWithActivityIndicator/ActivityIndicator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActivityIndicator.swift 3 | // testswiftui 4 | // 5 | // Created by Ali Adam on 6/12/19. 6 | // Copyright © 2019 AliAdam. All rights reserved. 7 | // 8 | import SwiftUI 9 | 10 | @available(iOS 13.0, *) 11 | public struct ActivityIndicator: UIViewRepresentable { 12 | 13 | let style: UIActivityIndicatorView.Style 14 | 15 | public func makeUIView(context: UIViewRepresentableContext) -> UIActivityIndicatorView { 16 | return UIActivityIndicatorView(style: style) 17 | } 18 | 19 | public func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext) { 20 | uiView.startAnimating() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/ImageWithActivityIndicator/ImageWithActivityIndicator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUIView.swift 3 | // testswiftui 4 | // 5 | // Created by Ali Adam on 6/12/19. 6 | // Copyright © 2019 AliAdam. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | @available(iOS 13.0, *) 13 | public struct ViewWithActivityIndicator : View { 14 | 15 | private let style: UIActivityIndicatorView.Style = .medium 16 | 17 | @ObservedObject private var viewLoader:ViewLoader 18 | private var content: () -> Content 19 | private let placeHolder:String 20 | private let showActivityIndicator:Bool 21 | 22 | public init(placeHolder: String = "",showActivityIndicator:Bool = true, viewLoader:ViewLoader, @ViewBuilder _ content: @escaping () -> Content){ 23 | self.placeHolder = placeHolder 24 | self.showActivityIndicator = showActivityIndicator 25 | self.viewLoader = viewLoader 26 | self.content = content 27 | } 28 | 29 | public var body: some View { 30 | ZStack(){ 31 | if (viewLoader.data.isEmpty) { 32 | if (placeHolder != "") { 33 | Image(placeHolder) 34 | .resizable() 35 | .scaledToFit() 36 | } 37 | 38 | if showActivityIndicator { 39 | ActivityIndicator(style: .large) 40 | } 41 | } 42 | else{ 43 | content() 44 | } 45 | } 46 | .onAppear(perform: loadImage) 47 | } 48 | 49 | private func loadImage() { 50 | self.viewLoader.loadData() 51 | } 52 | 53 | } 54 | 55 | #if DEBUG 56 | 57 | struct ImageWithActivityIndicator_Previews: PreviewProvider { 58 | @available(iOS 13.0, *) 59 | static var previews: some View { 60 | Text("not used") 61 | } 62 | } 63 | #endif 64 | -------------------------------------------------------------------------------- /Sources/ImageWithActivityIndicator/ViewLoader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageLoader.swift 3 | // testswiftui 4 | // 5 | // Created by Ali Adam on 6/12/19. 6 | // Copyright © 2019 AliAdam. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | @available(iOS 13.0, *) 12 | public class ViewLoader: ObservableObject { 13 | 14 | @Published var data = Data() 15 | let url:String 16 | 17 | public init(url:String){ 18 | self.url = url 19 | } 20 | 21 | public func loadData() { 22 | guard let url = URL(string:url) else { 23 | return 24 | } 25 | 26 | URLSession.shared.dataTask(with: url){(data,_,_) in 27 | guard let data = data else {return} 28 | DispatchQueue.main.async { 29 | self.data = data 30 | } 31 | }.resume() 32 | } 33 | 34 | public func getData() -> Data { 35 | return data 36 | } 37 | 38 | public func getUrl() -> String { 39 | return url 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/ImageWithActivityIndicator/ViewLoaders.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by luca on 9/5/19. 6 | // 7 | 8 | @available(iOS 13.0, *) 9 | public struct ViewLoaders { 10 | var loaders: [ViewLoader] = [] 11 | init(urls: [String]) { 12 | for url in urls { 13 | loaders.append(ViewLoader(url: url)) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Tests/ImageWithActivityIndicatorTests/ImageWithActivityIndicatorTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import ImageWithActivityIndicator 3 | 4 | final class ImageWithActivityIndicatorTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | // XCTAssertEqual(ImageWithActivityIndicator().text, "Hello, World!") 10 | } 11 | 12 | static var allTests = [ 13 | ("testExample", testExample), 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Tests/ImageWithActivityIndicatorTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(ImageWithActivityIndicatorTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import ImageWithActivityIndicatorTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += ImageWithActivityIndicatorTests.allTests() 7 | XCTMain(tests) 8 | --------------------------------------------------------------------------------