I have a barcode generator app where you generate barcode and you can add to the related widget.
When you add to the widget, I want to add image and text.
For image is ok but I have problem with text under the image
I have a File Manager swift file which is:
import Foundation
extension FileManager {
static let appGroupContainerURL = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: "group.com.antoniocristiano.BarWidget")!`
static func clearAllFile() {
let fileManager = FileManager.default
let myDocuments = FileManager.appGroupContainerURL.appendingPathComponent(FileManager.qrCode)
do {
try fileManager.removeItem(at: myDocuments)
} catch {
return
}
}
}
extension FileManager {
static let qrCode = "qrCode.png"
}
extension String {
var isValidURL: Bool {
let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
if let match = detector.firstMatch(in: self, options: [], range: NSRange(location: 0, length: self.utf16.count)) {
// it is a link, if the match covers the whole string
return match.range.length == self.utf16.count
} else {
return false
}
}
}
And the code of the widget is:
var body: some View {
VStack {
imageFromFile
.resizable()
.scaledToFit()
Text("\(luckyNumberFromFile)")
}
}
var imageFromFile: Image {
let path = FileManager.appGroupContainerURL.appendingPathComponent(FileManager.qrCode).path
let img = UIImage(contentsOfFile: path) ?? UIImage(named: "qr")!
return Image(uiImage: img)
}
var luckyNumberFromFile: Int {
let url = FileManager.appGroupContainerURL.appendingPathComponent(FileManager.qrCode)
guard let text = try? String(contentsOf: url, encoding: .utf8) else { return 0 }
return Int(text) ?? 0
}
The image will update but not the text
Thank you very much for your help
Best
Related
Im new to swift and I was trying to make an app that can parse the from on a screenshot. I have the following code so far, and I wasnt able to figure out proper way to call the recognize function in my ContentView, any help is appreciated:
`
struct ContentView: View {
#State var selectedItems: PhotosPickerItem?
#State var selectedPhotoData: Data?
func recogText(selData: Data?)
{
if let selData = selData, let image = UIImage(data: selData){
guard let cgImage = image.cgImage else {return}
let handler = VNImageRequestHandler(cgImage: cgImage)
let request = VNDetectTextRectanglesRequest { request, error in
guard let observations = request.results as? [VNRecognizedTextObservation],
error == nil else {return}
let text = observations.compactMap({
$0.topCandidates(1).first?.string
}).joined(separator: ", ")
print(text.count)
}
do {
try handler.perform([request])
}
catch {
print("Unable to perform the requests: \(error).")
}
}
}
var body: some View {
VStack{
//Icon
mainImage()
//Button
PhotosPicker(selection: $selectedItems, matching: .images) {
Label("Select a photo", systemImage: "photo")
}
.tint(.blue)
.controlSize(.large)
.buttonStyle(.borderedProminent)
.onChange(of: selectedItems) { newItem in
Task {
if let data = try? await newItem?.loadTransferable(type: Data.self) {
selectedPhotoData = data
let _ = recogText(selData: data)
}
}
}
}
}
}
`
Expected a print of the parsed text but no output was found
Hello here is an example function that might help you. Of course you have to replace the TestImage with yours. This might work for you and you need to import Vision
func recogText() {
let textRecognitionRequest = VNRecognizeTextRequest { (request, error) in
// Insert code to process the text recognition results here
guard let observations = request.results as? [VNRecognizedTextObservation] else { return }
for observation in observations {
let topCandidate = observation.topCandidates(1).first
if let recognizedText = topCandidate?.string {
print(recognizedText)
}
}
}
textRecognitionRequest.recognitionLevel = .accurate
let image = UIImage(named: "TestImage")
let imageRequestHandler = VNImageRequestHandler(cgImage: (image?.cgImage!)!, options: [:])
do {
try imageRequestHandler.perform([textRecognitionRequest])
} catch {
print(error)
}
}
(fyi Note:The app has been made in swift wireframe but now swiftUI has been added through hostview to handle complex structure and load data easily*).
What i want to do: when registered user clicks on notification button, it shows the small tableview on top right corner where updates from the discussion forum and new books uploaded from server are notified.. Now i want to save the book offline in json format(through document directory if possible) when user clicks on specific book that was shown in the notification. below is the notification area where updates are shown after clicking on notification bell
and then it shows like this
now when the user clicks on tableview cell("book was newly added"), the book should be saved offline in json format.. i want to load that file later in swift structure that is created to handle the data in swiftUI.
the data that comes from the api is in array form(huge nested data). every time the unique user logs in , it takes the user token to show the notification. Below is the API manager class..
i'm, providing link as the character count is beyond S.O limit..
https://github.com/JigarDave102/APImanager.git
What i tried and don't know how to take next step after this:
struct GetBookResponse: Codable {
var message:String
var totalRecord:Int
var totalPage:Int
var nextPage:String
var books:[GetBook]
enum CodingKeys: String, CodingKey {
case message
case totalRecord
case totalPage
case nextPage
case books = "data"
}
}
struct GetBook: Codable {
var bookId: Int
var title: String
var content: String
var coverImage: String
enum CoddingKeys: String, CodingKey {
case bookId = "iBookId"
case title = "vTitle"
case content = "txContent"
case coverImage = "txCoverImage"
}
}
*and api example for one book: book_id can be used from anyone from following 58,77,76,75,73,58
http://vadtaldhambooks.com/api/v1/get_book?book_id=58*
data to save to local json, fetch from : txContent
and notification class
import UIKit
import PKHUD
import Alamofire
import EmptyDataSet_Swift
class NotificationsTableViewController: UITableViewController {
var notifications: NotificationsResponse?
var currentPage = 1
var totalPage = 0
var aryOfNotificationList = NSMutableArray()
override func viewDidLoad() {
super.viewDidLoad()
title = _appDelegate.getLocalize("kNotifications")
// tableView.emptyDataSetSource = self
// tableView.emptyDataSetDelegate = self
tableView.tableFooterView = UIView()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
//self.getNotifications(page: "\(currentPage)")
self.fetchNotificationList(page: currentPage)
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.aryOfNotificationList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "notificationCell", for: indexPath) as! NotificationTableViewCell
let json = self.aryOfNotificationList.object(at: indexPath.row)as! NSDictionary
if let str = json.value(forKey: "vTitle")as? String{
cell.titleLable.text = str
}
if let str = json.value(forKey: "vText")as? String{
cell.detailLable.text = str
}
if let str = json.value(forKey: "tCreatedAt")as? Int{
cell.datLabel.text = getDate(unixdate: str, timezone: "UTC")
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let json = self.aryOfNotificationList.object(at: indexPath.row)as! NSDictionary
if let dic = json.value(forKey: "txParameters")as? NSDictionary{
if let str = dic.value(forKey: "iBookId")as? Int{
getBookService(bookId: str)
}
}
}
}
extension NotificationsTableViewController: EmptyDataSetSource {
func title(forEmptyDataSet scrollView: UIScrollView) -> NSAttributedString? {
guard let font = UIFont(name: "NotoSansGujarati", size: 18) else {
// do something with attributes
return NSAttributedString(string: "Record not found.")
}
let attributes = [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: UIColor.secondaryColor]
return NSAttributedString(string: "Record not found.", attributes: attributes)
}
}
// MARK:- API
extension NotificationsTableViewController {
func fetchNotificationList(page:Int){
guard let urlEncodedString = (AppConstants.URL.getNotifications + "?pageno=\(page)").addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
return
}
HUD.show(.progress)
let url = URL(string: urlEncodedString)!
print(url)
var headers: HTTPHeaders = [
"Content-Type" : "application/x-www-form-urlencoded",
"Accept" : "application/json",
]
if let accessToken = User.current?.accessToken {
headers["Authorization"] = "Bearer \(accessToken)"
} else if let accessToken = UserDefaults.standard.string(forKey: "AccessToken") {
headers["Authorization"] = "Bearer \(accessToken)"
}
AF.request(urlEncodedString, method:.get, parameters:nil, headers: headers)
.responseJSON { response in
switch response.result {
case .success(let value):
if let json = value as? NSDictionary{
print(json)
if let str = json.value(forKey: "totalRecord")as? Int{
self.totalPage = str
}
if let ary = json.value(forKey: "data")as? NSArray{
for j in ary{
self.aryOfNotificationList.add(j as! NSDictionary)
}
}
}
if self.currentPage >= self.totalPage{
DispatchQueue.main.async {
HUD.hide()
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.reloadData()
}
}else{
self.currentPage = self.currentPage + 1
self.fetchNotificationList(page: self.currentPage)
}
case .failure(let error):
print(error.localizedDescription)
DispatchQueue.main.async {
HUD.hide()
}
}
}
}
func getBookService(bookId: Int) {
guard let urlEncodedString = (AppConstants.URL.getBook + "?book_id=\(bookId)").addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
return
}
HUD.show(.progress)
let url = URL(string: urlEncodedString)!
let task = URLSession.shared.dataTask(with: url) { [weak self] (data, response, error) in
DispatchQueue.main.async {
HUD.hide()
}
if let er = error {
print(er)
Utils.showAlertController(with: er.localizedDescription, viewController: self!)
return
}
guard let unwrappedData = data else { return }
do {
//print(String(data: unwrappedData, encoding: .utf8))
let getBooksResponse = try JSONDecoder().decode(GetBookResponse.self, from: unwrappedData)
guard getBooksResponse.books.count != 0 else {
DispatchQueue.main.async {
Utils.showAlertController(with: "No book found.", viewController: self!)
}
return
}
DispatchQueue.main.async { [weak self] in
for book in getBooksResponse.books {
var bookJson =
"""
"id": \(book.bookId),
"title": \(book.title),
"desc": \(book.content)
"""
_appDelegate.loadString(jsonString: &bookJson, type: .Book, langType: .Gujrati)
}
Utils.showAlertController(with: "Book is saved", viewController: self!)
}
} catch {
print("json error: \(error)")
}
}
task.resume()
}
func getDate(unixdate: Int, timezone: String) -> String {
let date = NSDate(timeIntervalSince1970: TimeInterval(unixdate))
let dayTimePeriodFormatter = DateFormatter()
dayTimePeriodFormatter.dateFormat = "dd/MM/YYYY"
dayTimePeriodFormatter.timeZone = (NSTimeZone(name: timezone)! as TimeZone)
let dateString = dayTimePeriodFormatter.string(from: date as Date)
return "\(dateString)"
}
}
Here is some code to download the book data from your server at:
"http://vadtaldhambooks.com/api/v1/get_book?book_id=58"
, and store it into a local file "testbookfile",
as a array of books. The code takes into consideration some of your previous questions/answers
and shows how to decode into an array of books the unusual json from the api, where the book
content txContent is a string (where it should be an array of book content).
Run the code with fetchData() in the DataModel, then click the "Save to file button" to save the data into a
local file called "testbookfile". Then comment out the fetchData() and use the loadFromFile() instead.
Since the call to the server is "http" instead of the required "https", you need to temporarily set your Info.plist
with:
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
The code:
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
// holds the books and functions to fetch and save them
class DataModel: ObservableObject, #unchecked Sendable {
#Published var books: [Book] = []
#Published var loading = false
let fileName = "testbookfile"
let urlString = "http://vadtaldhambooks.com/api/v1/get_book?book_id=58"
init() {
fetchData() // <-- to fetch the data from the server at urlString
// loadFromFile() // <-- get the data from local file, after it has been saved
}
func fetchData() {
Task {
let res: BookResponse? = await fetch()
if let response = res {
DispatchQueue.main.async {
self.books = response.books
}
}
}
}
// todo deal with errors
private func fetch<T: Decodable>() async -> T? {
if let url = URL(string: urlString) {
do {
let (data, _) = try await URLSession.shared.data(for: URLRequest(url: url))
let results = try JSONDecoder().decode(T.self, from: data)
return results
}
catch {
print("error: \(error)")
}
}
return nil
}
func loadFromFile() {
do {
let furl = try FileManager.default
.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent(fileName)
.appendingPathExtension("json")
let data = try Data(contentsOf: furl)
let theBooks = try JSONDecoder().decode([Book].self, from: data)
DispatchQueue.main.async {
self.books = theBooks
}
} catch {
print("error: \(error)")
}
}
func saveToFile() {
do {
let furl = try FileManager.default
.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent(fileName)
.appendingPathExtension("json")
print("---> writing file to: \(furl)")
let data = try JSONEncoder().encode(books)
try data.write(to: furl)
} catch {
print("---> error saveToFile: \(error)")
}
}
}
extension View {
func attString(_ str: String) -> AttributedString {
attributedString(from: str, font: Font.system(size: 22))
}
func attributedString(from str: String, font: Font) -> AttributedString {
if let theData = str.data(using: .utf16) {
do {
let theString = try NSAttributedString(data: theData, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil)
var attaString = AttributedString(theString)
attaString.font = font
attaString.backgroundColor = .yellow
return attaString
} catch {
print("\(error)")
}
}
return AttributedString(str)
}
}
struct BookView: View {
#State var bookContent: BookContent
var body: some View {
ScrollView {
Text(attString(bookContent.title))
}.background(Color.yellow)
}
}
struct ContentView: View {
#StateObject var dataModel = DataModel()
var body: some View {
NavigationView {
VStack(alignment: .leading, spacing: 22) {
ForEach(dataModel.books) { book in
ScrollView {
LazyVStack(alignment: .leading, spacing: 22) {
Section(header: Text(book.title).font(.largeTitle).fontWeight(.heavy).foregroundColor(.yellow).padding(10)) {
ForEach(book.content) { bookContent in
OutlineGroup(bookContent.child ?? [], children: \.child) { item in
NavigationLink(destination: BookView(bookContent: item)) {
Text(bookContent.title).fontWeight(.heavy)
}.padding(10)
}
}
}.onAppear { dataModel.loading = false }
}
}
}
}
// remove the toolbar, after the data has been saved to file
.toolbar {
ToolbarItem(placement: .primaryAction) {
VStack {
if dataModel.loading {
ProgressView("Loading data …")
} else {
Button("Save to file") {
dataModel.saveToFile()
}
.padding(20)
.buttonStyle(BorderedButtonStyle())
}
}.padding(.top, 20)
}
}
.navigationTitle("Book list")
}.navigationViewStyle(.stack)
.onAppear { dataModel.loading = true }
}
}
struct BookResponse: Codable, Sendable {
var message: String = ""
var totalRecord: Int = 0
var totalPage: Int = 0
var nextPage: String = ""
var books: [Book] = []
enum CodingKeys: String, CodingKey {
case message
case totalRecord
case totalPage
case nextPage
case books = "data"
}
}
struct Book: Identifiable, Codable, Sendable {
let id = UUID().uuidString
var bookId: Int
var title: String
var content: [BookContent] = []
var coverImage: String
enum CodingKeys: String, CodingKey {
case bookId = "iBookId"
case title = "vTitle"
case content = "txContent"
case coverImage = "txCoverImage"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
bookId = try values.decode(Int.self, forKey: .bookId)
title = try values.decode(String.self, forKey: .title)
coverImage = try values.decode(String.self, forKey: .coverImage)
// decode the book content, when it comes as a String
if let theString = try? values.decode(String.self, forKey: .content) {
if let data = theString.data(using: .utf16) {
do {
content = try JSONDecoder().decode([BookContent].self, from: data)
} catch {
print("----> Book content decoding error: \(error)")
}
}
} else {
content = try values.decode([BookContent].self, forKey: .content)
}
}
}
struct BookContent: Identifiable, Codable, Sendable {
let id = UUID().uuidString
var title, type: String
var child: [BookContent]?
}
I am trying to display rich links in a SwiftUI List and no matter what I try, I can't seem to be able to change the size of the link view (UIViewRepresentable) on screen.
Is there a minimum size for a particular link? And how can I get it. Adding .aspectRatio and clipped() will respect size but the link is heavily clipped. Not sure why the link will not adjust aspectRatio to fit view.
Some of the following code is sourced from the following tutorial:
https://www.appcoda.com/linkpresentation-framework/
I am using the following UIViewRepresentable for the LinkView:
import SwiftUI
import LinkPresentation
struct LinkViewRepresentable: UIViewRepresentable {
typealias UIViewType = LPLinkView
var metadata: LPLinkMetadata?
func makeUIView(context: Context) -> LPLinkView {
guard let metadata = metadata else { return LPLinkView() }
let linkView = LPLinkView(metadata: metadata)
return linkView
}
func updateUIView(_ uiView: LPLinkView, context: Context) {
}
}
And my view with List is:
import SwiftUI
import LinkPresentation
struct ContentView: View {
#ObservedObject var linksViewModel = LinksViewModel()
var links: [(String, String)] = [("https://www.apple.com", "1"), ("https://www.stackoverflow.com", "2")]
var body: some View {
ScrollView(.vertical) {
LazyVStack {
ForEach(links, id: \.self.1) { link in
VStack {
Text(link.0)
.onAppear {
linksViewModel.getLinkMetadata(link: link)
}
if let richLink = linksViewModel.links.first(where: { $0.id == link.1 }) {
if let metadata = richLink.metadata {
if metadata.url != nil {
LinkViewRepresentable(metadata: metadata)
.frame(width: 200) // setting frame dimensions here has no effect
}
}
}
}
}
}
.padding()
}
}
}
Setting the frame of the view or contentMode(.fit) or padding or anything else I've tried does not change the size of the frame of the LinkViewRepresentable. I have tried sizeToFit in the representable on update and no luck. Is it possible to control the size of the representable view here?
Here are additional Files:
import Foundation
import LinkPresentation
class LinksViewModel: ObservableObject {
#Published var links = [Link]()
init() {
loadLinks()
}
func createLink(with metadata: LPLinkMetadata, id: String) {
let link = Link()
link.id = id
link.metadata = metadata
links.append(link)
saveLinks()
}
fileprivate func saveLinks() {
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: links, requiringSecureCoding: true)
guard let docDirURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
try data.write(to: docDirURL.appendingPathComponent("links"))
print(docDirURL.appendingPathComponent("links"))
} catch {
print(error.localizedDescription)
}
}
fileprivate func loadLinks() {
guard let docDirURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let linksURL = docDirURL.appendingPathComponent("links")
if FileManager.default.fileExists(atPath: linksURL.path) {
do {
let data = try Data(contentsOf: linksURL)
guard let unarchived = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? [Link] else { return }
links = unarchived
} catch {
print(error.localizedDescription)
}
}
}
func fetchMetadata(for link: String, completion: #escaping (Result<LPLinkMetadata, Error>) -> Void) {
guard let uRL = URL(string: link) else { return }
let metadataProvider = LPMetadataProvider()
metadataProvider.startFetchingMetadata(for: uRL) { (metadata, error) in
if let error = error {
print(error)
completion(.failure(error))
return
}
if let metadata = metadata {
completion(.success(metadata))
}
}
}
func getLinkMetadata(link: (String, String)) {
for storedLink in self.links {
if storedLink.id != link.1 {
return
}
}
do {
let detector = try NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let matches = detector.matches(in: link.0, options: [], range: NSRange(location: 0, length: link.0.utf16.count))
if let match = matches.first {
guard let range = Range(match.range, in: link.0) else { return }
let uRLString = link.0[range]
self.fetchMetadata(for: String(uRLString)) { result in
self.handleLinkFetchResult(result, link: link)
}
}
} catch {
print(error)
}
}
private func handleLinkFetchResult(_ result: Result<LPLinkMetadata, Error>, link: (String, String)) {
DispatchQueue.main.async {
switch result {
case .success(let metadata):
self.createLink(with: metadata, id: link.1)
case .failure(let error):
print(error.localizedDescription)
}
}
}
}
And Link Class:
import Foundation
import LinkPresentation
class Link: NSObject, NSSecureCoding, Identifiable {
var id: String?
var metadata: LPLinkMetadata?
override init() {
super.init()
}
// MARK: - NSSecureCoding Requirements
static var supportsSecureCoding = true
func encode(with coder: NSCoder) {
guard let id = id, let metadata = metadata else { return }
coder.encode(id, forKey: "id")
coder.encode(metadata as NSObject, forKey: "metadata")
}
required init?(coder: NSCoder) {
id = coder.decodeObject(forKey: "id") as? String
metadata = coder.decodeObject(of: LPLinkMetadata.self, forKey: "metadata")
}
}
This is what I get:
The solution that worked for me was subclassing the linkView overriding the intrinsic content size. Thanks to user1046037's comment, using super.intrinsicContentSize.height will enable it to work dynamically.
import SwiftUI
import LinkPresentation
class CustomLinkView: LPLinkView {
override var intrinsicContentSize: CGSize { CGSize(width: 0, height: super.intrinsicContentSize.height) }
}
struct LinkViewRepresentable: UIViewRepresentable {
typealias UIViewType = CustomLinkView
var metadata: LPLinkMetadata?
func makeUIView(context: Context) -> CustomLinkView {
guard let metadata = metadata else { return CustomLinkView() }
let linkView = CustomLinkView(metadata: metadata)
return linkView
}
func updateUIView(_ uiView: CustomLinkView, context: Context) {
}
}
I am trying to get images from a URL and append them to an array. This if my function for it:
func doStuff(html: String?){
do {
let doc: Document = try SwiftSoup.parse(html ?? "")
let priceClasses: Elements? = try doc.select("[class~=(?i)price]")
for priceClass: Element in priceClasses!.array() {
let priceText : String = try priceClass.text()
print(try priceClass.className())
print("pricetext: \(priceText)")
}
let srcs: Elements = try doc.select("img[src]")
let srcsStringArray: [String?] = srcs.array().map { try? $0.attr("src").description }
for imageName in srcsStringArray {
if (imageName?.matches("^https?://(?:[a-z0-9\\-]+\\.)+[a-z]{2,6}(?:/[^/#?]+)+\\.(?:jpg|gif|png)$"))! {
print(imageName!)
let imageView = UIImageView()
imageView.downloaded(from: imageName!) {
if let image = imageView.image {
self.imagesArray!.append(image)
} else {
print("Image '\(String(describing: imageName))' does not exist!")
}
}
}
}
} catch Exception.Error( _, let message) {
print(message)
} catch {
print("error")
}
}
This code is not working as it always exits and print <imageName> does not exist! . The weird thing is that the fileName is a correct name!
For example:
https://www.adidas.de/on/demandware.static/-/Sites-adidas-DE-Library/default/dw817801e3/Originals_Brand_Nav_Title.png
This is how I download the image from the URL:
extension UIImageView {
func downloaded(from url: URL, contentMode mode: UIView.ContentMode = .scaleAspectFit) {
contentMode = mode
URLSession.shared.dataTask(with: url) { data, response, error in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
let data = data, error == nil,
let image = UIImage(data: data)
else { return }
DispatchQueue.main.async() {
self.image = image
}
}.resume()
}
func downloaded(from link: String, contentMode mode: UIView.ContentMode = .scaleAspectFit, finished: () -> Void) {
guard let url = URL(string: link) else { return }
downloaded(from: url, contentMode: mode)
finished()
}
}
Does anyone have any idea why I can not append images to my array ??? Im stuck..
I fixed the issue by changing the way I load the image:
extension UIImage {
public static func loadFrom(url: URL, completion: #escaping (_ image: UIImage?) -> ()) {
DispatchQueue.global().async {
if let data = try? Data(contentsOf: url) {
DispatchQueue.main.async {
completion(UIImage(data: data))
}
} else {
DispatchQueue.main.async {
completion(nil)
}
}
}
}
}
With this I simply call:
for imageName in srcsStringArray {
if (imageName?.matches("^https?://(?:[a-z0-9\\-]+\\.)+[a-z]{2,6}(?:/[^/#?]+)+\\.(?:jpg|gif|png)$"))! {
guard let url = URL(string: imageName!) else { return }
UIImage.loadFrom(url: url) { image in
if let image = image {
print("append")
self.imagesArray.append(image)
} else {
print("Image '\(String(describing: imageName))' does not exist!")
}
}
}
}
I am trying to complet an an action after the URLSession resumes.
So I am downloading several images from my server with the url, which all works good. But now I am trying to save those images to the disk after I have finished downloading them.
Problem
Now I can save them inside the same query while downloading them but I would prefer not too as it makes my query slower.
So I have added a completion handler to my func with the query, but when I save the images to the disk in that block it works but I cannot do anything with my screen as the query has not resumed yet it is blocked from touches I guess...
Now I would like to be able to call my func to save the images to the disk straight after the query has been resumed.... Anyone have any idea?
If someone needs more explanation or to see code just drop a comment below
Many thanks in advance to anyone that can help!
Code for downloading
func loadPosts(completionHandler: #escaping (Bool) -> ()) {
pageNumber = 1
appDelegate.setNetworkActivityIndicatorVisible(true)
let id = user!["id"] as! String
let url = URL(string: "http://************/Files/Posts.php")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let body = "id=\(id)&caption=&uuid=&page="
request.httpBody = body.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request, completionHandler: { (data:Data?, response:URLResponse?, error:Error?) in
DispatchQueue.global(qos: .background).async {
if error == nil {
let oldImageArray = self.cellContentArray
self.cellContentArray.removeAll(keepingCapacity: false)
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
guard let parseJSON = json else {
print("Error while parsing")
return
}
guard let posts = parseJSON["Posts"] as? [AnyObject] else {
print("Error while parseJSONing")
return
}
for element in posts {
// here I download the stuff and it to my Array, too long and no point to show here
}
let oldImageSet = Set(oldImageArray.map({return $0.uuid}))
let newImageSet = Set(self.cellContentArray.map({return $0.uuid}))
let addedImages = newImageSet.subtracting(oldImageSet)
let addedImageSections = Array(addedImages).map{ self.cellContentArray.map({return $0.uuid}).index(of: $0)! }
let addedImageIndexSet = IndexSet(addedImageSections)
let removedImages = oldImageSet.subtracting(newImageSet)
let removedImageSections = Array(removedImages).map{ oldImageArray.map({return $0.uuid}).index(of: $0)! }
let removedImageIndexSet = IndexSet(removedImageSections)
if !addedImageIndexSet.isEmpty {
if oldImageArray.count >= 5 {
self.lastUUIDImage = oldImageArray[4].uuid
} else {
}
self.coreDataShit()
}
DispatchQueue.main.async{
print(placeholderImage.count)
if placeholderImage.count > 5 {
placeholderImage.removeFirst(placeholderImage.count - 5)
}
print("finished")
self.customView.isHidden = true
if posts.count >= 5 {
self.tableView.addInfiniteScroll { [weak self] (scrollView) -> Void in
self?.loadMore()
}}
self.activityView.stopAnimating()
self.internetView.removeFromSuperview()
self.tableView.beginUpdates()
if !addedImageIndexSet.isEmpty {
self.tableView.insertSections(addedImageIndexSet, with: .top)
}
if !removedImageIndexSet.isEmpty {
self.tableView.deleteSections(removedImageIndexSet, with: .bottom)
}
self.tableView.endUpdates()
self.tableView.finishInfiniteScroll()
self.refresher.endRefreshing()
appDelegate.setNetworkActivityIndicatorVisible(false)
completionHandler(true)
}
} catch {
DispatchQueue.main.async {
self.tableView.removeInfiniteScroll()
self.customView.isHidden = false
self.refresher.endRefreshing()
self.tableView.reloadData()
}
}
} else {
DispatchQueue.main.async(execute: {
let message = error!.localizedDescription
appDelegate.infoView(message: message, color: smoothRedColor)
})
}
}
})
task.resume()
}
Saving Image
self.loadPosts(completionHandler: { (true) in
print("completion")
let sections = self.tableView.numberOfSections
for i in 0..<sections {
self.rows += self.tableView.numberOfRows(inSection: i)
}
print(self.rows)
if self.rows <= 5 {
print("less than 5")
print(self.rows)
var i = 0
for element in self.cellContentArray {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let dirPath = "\(path)/images"
let url = NSURL(fileURLWithPath: dirPath)
let filePath = url.appendingPathComponent("\(element.uuid).jpg")?.path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath!) {
print("File exsists")
} else {
print("File doesn't exsist")
DispatchQueue.main.async {
let url = NSURL(string: element.fullImage!)! // convert path str to url
let imageData = NSData(contentsOf: url as URL) // get data via url and assigned imageData
let imageName = element.uuid
let saveImages = FileSaveHelper(fileName: imageName, fileExtension: .JPG, subDirectory: "images", directory: .documentDirectory)
do {
guard let image = UIImage.sd_image(with: imageData as Data!) else {
print("Error getting image")
return
}
try saveImages.saveFile(image: image)
self.saveNewImagePath(imageLink: imageName, uuid: imageName)
self.removeImage(itemName: "file\(i)", fileExtension: "jpg")
self.removeImage(itemName: self.lastUUIDImage, fileExtension: "jpg")
i += 1
} catch {
print(error)
}
}
}
}
}
})
Image in tableView Cell
self.postImage.sd_setImage(with: URL(string: content.fullImage!), placeholderImage: placeHolder, options: .retryFailed) { (image:UIImage?, error:Error?, cached:SDImageCacheType, url:URL?) in
}
In your code of saving image this line of code is blocking you UI
let imageData = NSData(contentsOf: url as URL) // get data via url and assigned imageData
This is not proper way to download image from server, you should download image asynchronously using URLSession